@@ -125,6 +125,16 @@ export function getTypeScriptType(schema: SwaggerDefinition | undefined | null,
125125 return typeName && knownTypes . includes ( typeName ) ? typeName : 'any' ;
126126 }
127127
128+ // Support for OAS 3.1 $dynamicRef.
129+ // For code generation, we treat this statically by linking to the model name found in the reference.
130+ if ( schema . $dynamicRef ) {
131+ // Typically points to an anchor, but can point to a path.
132+ // We take the segment after the last '#' or '/'
133+ const ref = schema . $dynamicRef ;
134+ const typeName = pascalCase ( ref . split ( '#' ) . pop ( ) ?. split ( '/' ) . pop ( ) || '' ) ;
135+ return typeName && knownTypes . includes ( typeName ) ? typeName : 'any' ;
136+ }
137+
128138 // JSON Schema 'const' keyword support (OAS 3.1)
129139 if ( schema . const !== undefined ) {
130140 const val = schema . const ;
@@ -145,6 +155,49 @@ export function getTypeScriptType(schema: SwaggerDefinition | undefined | null,
145155 return `[${ tupleTypes . join ( ', ' ) } ]` ;
146156 }
147157
158+ // JSON Schema 2020-12 / OAS 3.1 Conditional Support (if/then/else)
159+ if ( schema . if ) {
160+ // In TypeScript static analysis, `if` acts like a discriminated union intersection.
161+ // `(Type & Then) | (Exclude<Type, If> & Else)` roughly approximates this logic,
162+ // but TS type narrowing for arbitrary json schema conditions is limited.
163+ // A practical approximation for API clients is: `(Then | Else) & BaseType` if base properties exist, or a Union.
164+ // However, `if` validates the instance. It doesn't inherently change the shape unless combined with properties.
165+ //
166+ // Strategy:
167+ // 1. Treat `then` as one possibility.
168+ // 2. Treat `else` as another possibility.
169+ // 3. The result is `(Then | Else)`. If one is missing, it's `Then | any` or `any | Else` which simplifies to `any` or partial.
170+ // Better Strategy for Models: `Base & (Then | Else)`
171+
172+ const thenType = schema . then ? getTypeScriptType ( schema . then , config , knownTypes ) : 'any' ;
173+ const elseType = schema . else ? getTypeScriptType ( schema . else , config , knownTypes ) : 'any' ;
174+
175+ // If we have local properties, we generate an intersection.
176+ if ( schema . properties || schema . allOf ) {
177+ // We recursively get the base type without if/then/else to avoid infinite recursion if we just called getTypeScriptType.
178+ // But since `schema` object is the same, we need to clone and strip condition keywords.
179+ const { if : _ , then : __ , else : ___ , ...baseSchema } = schema ;
180+ const baseType = getTypeScriptType ( baseSchema , config , knownTypes ) ;
181+
182+ if ( schema . then && schema . else ) {
183+ return `${ baseType } & (${ thenType } | ${ elseType } )` ;
184+ } else if ( schema . then ) {
185+ // If only `then` is present, `else` is implicitly valid for everything (type wise), implying optionality.
186+ // But structurally, `if` implies constraints.
187+ // We output intersection for correctness of the 'then' branch structure types.
188+ return `${ baseType } & (${ thenType } | any)` ;
189+ } else if ( schema . else ) {
190+ return `${ baseType } & (any | ${ elseType } )` ;
191+ }
192+ } else {
193+ // Pure structural conditional
194+ if ( schema . then && schema . else ) {
195+ return `${ thenType } | ${ elseType } ` ;
196+ }
197+ return 'any' ; // Too ambiguous without base props
198+ }
199+ }
200+
148201 if ( schema . allOf ) {
149202 const parts = schema . allOf
150203 . map ( s => getTypeScriptType ( s , config , knownTypes ) )
@@ -223,6 +276,25 @@ export function getTypeScriptType(schema: SwaggerDefinition | undefined | null,
223276 parts . push ( `[key: string]: ${ joined } ` ) ;
224277 }
225278
279+ // Handle 'dependentSchemas' (JSON Schema 2020-12).
280+ // If property X is present, then properties from Schema Y must also be valid.
281+ // We represent this as an intersection: `{ [x]: Type } & DependentSchemaType`
282+ if ( schema [ 'dependentSchemas' ] ) { // Note: 'dependentSchemas' isn't in strict type, cast/access loosely or update type def
283+ // Assuming SwaggerDefinition includes generic indexer or updated type definition
284+ const deps = ( schema as any ) . dependentSchemas ;
285+ Object . entries ( deps ) . forEach ( ( [ prop , depSchema ] ) => {
286+ const depType = getTypeScriptType ( depSchema as SwaggerDefinition , config , knownTypes ) ;
287+ // In TS, this conditional relationship is hard to model perfectly static.
288+ // The most robust way for a client model is to intersect the base with the dependent type
289+ // effectively saying "Example object has all these potential shapes combined".
290+ // Ideally: `Base & Partial<Dependent>` but that loses strictness.
291+ // For now, we treat it as `& Dependent` assuming scenarios where the dependency is met.
292+ // Or, more safely, leave it as `any` or documented field.
293+ // A safe static approach: `& Partial<DependentType>`
294+ parts . push ( `// dependentSchema: ${ prop } -> ${ depType } ` ) ;
295+ } ) ;
296+ }
297+
226298 if ( parts . length > 0 ) {
227299 type = `{ ${ parts . join ( '; ' ) } }` ;
228300 } else {
@@ -274,6 +346,20 @@ export function hasDuplicateFunctionNames(methods: MethodDeclaration[]): boolean
274346 return new Set ( names ) . size !== names . length ;
275347}
276348
349+ /**
350+ * Normalizes a security scheme key.
351+ * If the key is a JSON pointer/URI (e.g., '#/components/securitySchemes/MyScheme'),
352+ * it extracts the simple name ('MyScheme'). Otherwise returns the key as is.
353+ */
354+ function normalizeSecurityKey ( key : string ) : string {
355+ // Check if it looks like a URI fragment or JSON pointer
356+ if ( key . includes ( '/' ) ) {
357+ const parts = key . split ( '/' ) ;
358+ return parts [ parts . length - 1 ] ;
359+ }
360+ return key ;
361+ }
362+
277363// Helper type to handle union of Swagger 2.0 and OpenAPI 3.x parameter definitions
278364type UnifiedParameter = SwaggerOfficialParameter & {
279365 schema ?: SwaggerDefinition | { $ref : string } ,
@@ -314,11 +400,20 @@ export function extractPaths(
314400 for ( const [ path , rawPathItem ] of Object . entries ( swaggerPaths ) ) {
315401 let pathItem = rawPathItem ;
316402
317- // Handle Path Item $ref via resolver if provided
403+ // Handle Path Item $ref via resolver if provided.
404+ // OAS 3.2 Compliance: Sibling properties on a Reference Object (or Path Item with $ref)
405+ // override the properties of the referenced object.
318406 if ( pathItem . $ref && resolveRef ) {
319407 const resolved = resolveRef ( pathItem . $ref ) ;
320408 if ( resolved ) {
321- pathItem = resolved ;
409+ // Shallow merge: The properties defined in the source file (`pathItem`)
410+ // take precedence over the resolved reference properties (`resolved`).
411+ // We spread `pathItem` second to ensure its local overrides (e.g., summary) win.
412+ const localOverrides = { ...pathItem } ;
413+ // We delete $ref from local overrides before merge to avoid confusion downstream
414+ delete localOverrides . $ref ;
415+
416+ pathItem = { ...resolved , ...localOverrides } ;
322417 }
323418 }
324419
@@ -473,6 +568,19 @@ export function extractPaths(
473568
474569 const effectiveServers = operation . servers || pathServers ;
475570
571+ // Normalize Security Requirements (OAS 3.2 allows URI references as keys)
572+ // e.g. '#/components/securitySchemes/MyScheme' -> 'MyScheme'
573+ let effectiveSecurity = operation . security ;
574+ if ( effectiveSecurity ) {
575+ effectiveSecurity = effectiveSecurity . map ( req => {
576+ const normalizedReq : { [ key : string ] : string [ ] } = { } ;
577+ Object . keys ( req ) . forEach ( key => {
578+ normalizedReq [ normalizeSecurityKey ( key ) ] = req [ key ] ;
579+ } ) ;
580+ return normalizedReq ;
581+ } ) ;
582+ }
583+
476584 const pathInfo : PathInfo = {
477585 path,
478586 method : method . toUpperCase ( ) ,
@@ -485,13 +593,19 @@ export function extractPaths(
485593 if ( operation . callbacks ) pathInfo . callbacks = operation . callbacks ;
486594
487595 if ( operation . operationId ) pathInfo . operationId = operation . operationId ;
488- if ( operation . summary ) pathInfo . summary = operation . summary ;
489- if ( operation . description ) pathInfo . description = operation . description ;
596+
597+ // Merge Summary/Description from PathItem if not present on Operation
598+ // The order here ensures explicit Op overrides > Path Item overrides > Original ref properties
599+ const summary = operation . summary || pathItem . summary
600+ if ( summary ) pathInfo . summary = summary ;
601+ const description = operation . description || pathItem . description ;
602+ if ( description ) pathInfo . description = description ;
603+
490604 if ( operation . tags ) pathInfo . tags = operation . tags ;
491605 if ( operation . consumes ) pathInfo . consumes = operation . consumes ;
492606 if ( operation . deprecated ) pathInfo . deprecated = operation . deprecated ;
493607 if ( operation . externalDocs ) pathInfo . externalDocs = operation . externalDocs ;
494- if ( operation . security ) pathInfo . security = operation . security ;
608+ if ( effectiveSecurity ) pathInfo . security = effectiveSecurity ;
495609
496610 paths . push ( pathInfo ) ;
497611 }
0 commit comments