@@ -92,72 +92,79 @@ export function transformSchemaObjectWithComposition(
9292 if (
9393 Array . isArray ( schemaObject . enum ) &&
9494 ( ! ( "type" in schemaObject ) || schemaObject . type !== "object" ) &&
95- ! ( "properties" in schemaObject ) &&
96- ! ( "additionalProperties" in schemaObject )
95+ ! ( "properties" in schemaObject )
9796 ) {
98- // hoist enum to top level if string/number enum and option is enabled
99- if (
100- options . ctx . enum &&
101- schemaObject . enum . every ( ( v ) => typeof v === "string" || typeof v === "number" || v === null )
102- ) {
103- let enumName = parseRef ( options . path ?? "" ) . pointer . join ( "/" ) ;
104- // allow #/components/schemas to have simpler names
105- enumName = enumName . replace ( "components/schemas" , "" ) ;
106- const metadata = schemaObject . enum . map ( ( _ , i ) => ( {
107- name : schemaObject [ "x-enum-varnames" ] ?. [ i ] ?? schemaObject [ "x-enumNames" ] ?. [ i ] ,
108- description : schemaObject [ "x-enum-descriptions" ] ?. [ i ] ?? schemaObject [ "x-enumDescriptions" ] ?. [ i ] ,
109- } ) ) ;
110-
111- // enums can contain null values, but dont want to output them
112- let hasNull = false ;
113- const validSchemaEnums = schemaObject . enum . filter ( ( enumValue ) => {
114- if ( enumValue === null ) {
115- hasNull = true ;
116- return false ;
97+ const hasAdditionalProperties = "additionalProperties" in schemaObject && ! ! schemaObject . additionalProperties ;
98+
99+ if ( ! hasAdditionalProperties || ( schemaObject . type === "string" && hasAdditionalProperties ) ) {
100+ // hoist enum to top level if string/number enum and option is enabled
101+ if (
102+ options . ctx . enum &&
103+ schemaObject . enum . every ( ( v ) => typeof v === "string" || typeof v === "number" || v === null )
104+ ) {
105+ let enumName = parseRef ( options . path ?? "" ) . pointer . join ( "/" ) ;
106+ // allow #/components/schemas to have simpler names
107+ enumName = enumName . replace ( "components/schemas" , "" ) ;
108+ const metadata = schemaObject . enum . map ( ( _ , i ) => ( {
109+ name : schemaObject [ "x-enum-varnames" ] ?. [ i ] ?? schemaObject [ "x-enumNames" ] ?. [ i ] ,
110+ description : schemaObject [ "x-enum-descriptions" ] ?. [ i ] ?? schemaObject [ "x-enumDescriptions" ] ?. [ i ] ,
111+ } ) ) ;
112+
113+ // enums can contain null values, but dont want to output them
114+ let hasNull = false ;
115+ const validSchemaEnums = schemaObject . enum . filter ( ( enumValue ) => {
116+ if ( enumValue === null ) {
117+ hasNull = true ;
118+ return false ;
119+ }
120+
121+ return true ;
122+ } ) ;
123+ const enumType = tsEnum ( enumName , validSchemaEnums as ( string | number ) [ ] , metadata , {
124+ shouldCache : options . ctx . dedupeEnums ,
125+ export : true ,
126+ // readonly: TS enum do not support the readonly modifier
127+ } ) ;
128+ if ( ! options . ctx . injectFooter . includes ( enumType ) ) {
129+ options . ctx . injectFooter . push ( enumType ) ;
117130 }
131+ const ref = ts . factory . createTypeReferenceNode ( enumType . name ) ;
132+
133+ const finalType : ts . TypeNode = hasNull ? tsUnion ( [ ref , NULL ] ) : ref ;
118134
119- return true ;
120- } ) ;
121- const enumType = tsEnum ( enumName , validSchemaEnums as ( string | number ) [ ] , metadata , {
122- shouldCache : options . ctx . dedupeEnums ,
123- export : true ,
124- // readonly: TS enum do not support the readonly modifier
125- } ) ;
126- if ( ! options . ctx . injectFooter . includes ( enumType ) ) {
127- options . ctx . injectFooter . push ( enumType ) ;
135+ return applyAdditionalPropertiesToEnum ( hasAdditionalProperties , finalType , schemaObject ) ;
128136 }
129- const ref = ts . factory . createTypeReferenceNode ( enumType . name ) ;
130- return hasNull ? tsUnion ( [ ref , NULL ] ) : ref ;
131- }
132- const enumType = schemaObject . enum . map ( tsLiteral ) ;
133- if ( ( Array . isArray ( schemaObject . type ) && schemaObject . type . includes ( "null" ) ) || schemaObject . nullable ) {
134- enumType . push ( NULL ) ;
135- }
136137
137- const unionType = tsUnion ( enumType ) ;
138+ const enumType = schemaObject . enum . map ( tsLiteral ) ;
139+ if ( ( Array . isArray ( schemaObject . type ) && schemaObject . type . includes ( "null" ) ) || schemaObject . nullable ) {
140+ enumType . push ( NULL ) ;
141+ }
138142
139- // hoist array with valid enum values to top level if string/number enum and option is enabled
140- if ( options . ctx . enumValues && schemaObject . enum . every ( ( v ) => typeof v === "string" || typeof v === "number" ) ) {
141- let enumValuesVariableName = parseRef ( options . path ?? "" ) . pointer . join ( "/" ) ;
142- // allow #/components/schemas to have simpler names
143- enumValuesVariableName = enumValuesVariableName . replace ( "components/schemas" , "" ) ;
144- enumValuesVariableName = `${ enumValuesVariableName } Values` ;
143+ const unionType = applyAdditionalPropertiesToEnum ( hasAdditionalProperties , tsUnion ( enumType ) , schemaObject ) ;
144+
145+ // hoist array with valid enum values to top level if string/number enum and option is enabled
146+ if ( options . ctx . enumValues && schemaObject . enum . every ( ( v ) => typeof v === "string" || typeof v === "number" ) ) {
147+ let enumValuesVariableName = parseRef ( options . path ?? "" ) . pointer . join ( "/" ) ;
148+ // allow #/components/schemas to have simpler names
149+ enumValuesVariableName = enumValuesVariableName . replace ( "components/schemas" , "" ) ;
150+ enumValuesVariableName = `${ enumValuesVariableName } Values` ;
151+
152+ const enumValuesArray = tsArrayLiteralExpression (
153+ enumValuesVariableName ,
154+ oapiRef ( options . path ?? "" ) ,
155+ schemaObject . enum as ( string | number ) [ ] ,
156+ {
157+ export : true ,
158+ readonly : true ,
159+ injectFooter : options . ctx . injectFooter ,
160+ } ,
161+ ) ;
145162
146- const enumValuesArray = tsArrayLiteralExpression (
147- enumValuesVariableName ,
148- oapiRef ( options . path ?? "" ) ,
149- schemaObject . enum as ( string | number ) [ ] ,
150- {
151- export : true ,
152- readonly : true ,
153- injectFooter : options . ctx . injectFooter ,
154- } ,
155- ) ;
163+ options . ctx . injectFooter . push ( enumValuesArray ) ;
164+ }
156165
157- options . ctx . injectFooter . push ( enumValuesArray ) ;
166+ return unionType ;
158167 }
159-
160- return unionType ;
161168 }
162169
163170 /**
@@ -584,3 +591,16 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor
584591function hasKey < K extends string > ( possibleObject : unknown , key : K ) : possibleObject is { [ key in K ] : unknown } {
585592 return typeof possibleObject === "object" && possibleObject !== null && key in possibleObject ;
586593}
594+
595+ function applyAdditionalPropertiesToEnum (
596+ hasAdditionalProperties : boolean ,
597+ unionType : ts . TypeNode ,
598+ schemaObject : SchemaObject ,
599+ ) {
600+ // If additionalProperties is true, add (string & {}) to the union
601+ if ( hasAdditionalProperties && schemaObject . type === "string" ) {
602+ const stringAndEmptyObject = tsIntersection ( [ STRING , ts . factory . createTypeLiteralNode ( [ ] ) ] ) ;
603+ return tsUnion ( [ unionType , stringAndEmptyObject ] ) ;
604+ }
605+ return unionType ;
606+ }
0 commit comments