@@ -28,18 +28,53 @@ export class InvalidFieldError extends Error {
2828 }
2929}
3030
31+ const FILE_PROPERTIES = [ 'name' , 'type' , 'size' , 'url' , 'base64' , 'mimeType' ] as const
32+
33+ function isFileType ( value : unknown ) : boolean {
34+ if ( typeof value !== 'object' || value === null ) return false
35+ const typed = value as { type ?: string }
36+ return typed . type === 'file[]' || typed . type === 'files'
37+ }
38+
39+ function isArrayType ( value : unknown ) : value is { type : 'array' ; items ?: unknown } {
40+ if ( typeof value !== 'object' || value === null ) return false
41+ return ( value as { type ?: string } ) . type === 'array'
42+ }
43+
44+ function getArrayItems ( schema : unknown ) : unknown {
45+ if ( typeof schema !== 'object' || schema === null ) return undefined
46+ return ( schema as { items ?: unknown } ) . items
47+ }
48+
49+ function getProperties ( schema : unknown ) : Record < string , unknown > | undefined {
50+ if ( typeof schema !== 'object' || schema === null ) return undefined
51+ const props = ( schema as { properties ?: unknown } ) . properties
52+ return typeof props === 'object' && props !== null
53+ ? ( props as Record < string , unknown > )
54+ : undefined
55+ }
56+
57+ function lookupField ( schema : unknown , fieldName : string ) : unknown | undefined {
58+ if ( typeof schema !== 'object' || schema === null ) return undefined
59+ const typed = schema as Record < string , unknown >
60+
61+ if ( fieldName in typed ) {
62+ return typed [ fieldName ]
63+ }
64+
65+ const props = getProperties ( schema )
66+ if ( props && fieldName in props ) {
67+ return props [ fieldName ]
68+ }
69+
70+ return undefined
71+ }
72+
3173function isPathInSchema ( schema : OutputSchema | undefined , pathParts : string [ ] ) : boolean {
3274 if ( ! schema || pathParts . length === 0 ) {
3375 return true
3476 }
3577
36- const FILE_PROPERTIES = [ 'name' , 'type' , 'size' , 'url' , 'base64' , 'mimeType' ]
37- const isFileType = ( value : unknown ) : boolean => {
38- if ( typeof value !== 'object' || value === null ) return false
39- const typed = value as { type ?: string }
40- return typed . type === 'file[]' || typed . type === 'files'
41- }
42-
4378 let current : unknown = schema
4479
4580 for ( let i = 0 ; i < pathParts . length ; i ++ ) {
@@ -50,78 +85,58 @@ function isPathInSchema(schema: OutputSchema | undefined, pathParts: string[]):
5085 }
5186
5287 if ( / ^ \d + $ / . test ( part ) ) {
53- if ( isFileType ( current ) && i + 1 < pathParts . length ) {
54- return FILE_PROPERTIES . includes ( pathParts [ i + 1 ] )
88+ if ( isFileType ( current ) ) {
89+ const nextPart = pathParts [ i + 1 ]
90+ return ! nextPart || FILE_PROPERTIES . includes ( nextPart as ( typeof FILE_PROPERTIES ) [ number ] )
91+ }
92+ if ( isArrayType ( current ) ) {
93+ current = getArrayItems ( current )
5594 }
5695 continue
5796 }
5897
5998 const arrayMatch = part . match ( / ^ ( [ ^ [ ] + ) \[ ( \d + ) \] $ / )
6099 if ( arrayMatch ) {
61100 const [ , prop ] = arrayMatch
62- const typed = current as Record < string , unknown >
101+ const fieldDef = lookupField ( current , prop )
102+ if ( ! fieldDef ) return false
63103
64- if ( prop in typed ) {
65- const fieldDef = typed [ prop ]
66- if ( isFileType ( fieldDef ) && i + 1 < pathParts . length ) {
67- return FILE_PROPERTIES . includes ( pathParts [ i + 1 ] )
68- }
69- current = fieldDef
70- continue
104+ if ( isFileType ( fieldDef ) ) {
105+ const nextPart = pathParts [ i + 1 ]
106+ return ! nextPart || FILE_PROPERTIES . includes ( nextPart as ( typeof FILE_PROPERTIES ) [ number ] )
71107 }
72- return false
73- }
74108
75- const typed = current as Record < string , unknown >
76-
77- if ( part in typed ) {
78- const nextValue = typed [ part ]
79- if ( isFileType ( nextValue ) && i + 1 < pathParts . length ) {
80- if ( / ^ \d + $ / . test ( pathParts [ i + 1 ] ) && i + 2 < pathParts . length ) {
81- return FILE_PROPERTIES . includes ( pathParts [ i + 2 ] )
82- }
83- return FILE_PROPERTIES . includes ( pathParts [ i + 1 ] )
84- }
85- current = nextValue
109+ current = isArrayType ( fieldDef ) ? getArrayItems ( fieldDef ) : fieldDef
86110 continue
87111 }
88112
89- if ( typed . properties && typeof typed . properties === 'object' ) {
90- const props = typed . properties as Record < string , unknown >
91- if ( part in props ) {
92- current = props [ part ]
93- continue
94- }
113+ if ( isFileType ( current ) && FILE_PROPERTIES . includes ( part as ( typeof FILE_PROPERTIES ) [ number ] ) ) {
114+ return true
95115 }
96116
97- if ( typed . type === 'array' && typed . items && typeof typed . items === 'object' ) {
98- const items = typed . items as Record < string , unknown >
99- if ( items . properties && typeof items . properties === 'object' ) {
100- const itemProps = items . properties as Record < string , unknown >
101- if ( part in itemProps ) {
102- current = itemProps [ part ]
103- continue
117+ const fieldDef = lookupField ( current , part )
118+ if ( fieldDef !== undefined ) {
119+ if ( isFileType ( fieldDef ) ) {
120+ const nextPart = pathParts [ i + 1 ]
121+ if ( ! nextPart ) return true
122+ if ( / ^ \d + $ / . test ( nextPart ) ) {
123+ const afterIndex = pathParts [ i + 2 ]
124+ return (
125+ ! afterIndex || FILE_PROPERTIES . includes ( afterIndex as ( typeof FILE_PROPERTIES ) [ number ] )
126+ )
104127 }
128+ return FILE_PROPERTIES . includes ( nextPart as ( typeof FILE_PROPERTIES ) [ number ] )
105129 }
106- if ( part in items ) {
107- current = items [ part ]
108- continue
109- }
110- }
111-
112- if ( isFileType ( current ) && FILE_PROPERTIES . includes ( part ) ) {
113- return true
130+ current = fieldDef
131+ continue
114132 }
115133
116- if (
117- typeof current === 'object' &&
118- current !== null &&
119- 'type' in current &&
120- typeof ( current as { type : unknown } ) . type === 'string'
121- ) {
122- const typedCurrent = current as { type : string ; properties ?: unknown ; items ?: unknown }
123- if ( ! typedCurrent . properties && ! typedCurrent . items ) {
124- return false
134+ if ( isArrayType ( current ) ) {
135+ const items = getArrayItems ( current )
136+ const itemField = lookupField ( items , part )
137+ if ( itemField !== undefined ) {
138+ current = itemField
139+ continue
125140 }
126141 }
127142
0 commit comments