openapi-typescript
Advanced tools
Comparing version 7.0.0-next.8 to 7.0.0-next.9
@@ -82,2 +82,4 @@ # openapi-typescript | ||
- [#1602](https://github.com/drwpow/openapi-typescript/pull/1602) [`9da96cd`](https://github.com/drwpow/openapi-typescript/commit/9da96cda4eb8f959c4703637d8fc89e1d3532af1) Thanks [@JeanRemiDelteil](https://github.com/JeanRemiDelteil)! - Do not add readonly on Typescript enum when the --immutable option is used. | ||
## 6.7.0 | ||
@@ -84,0 +86,0 @@ |
@@ -42,3 +42,2 @@ import ts from "typescript"; | ||
}[], options?: { | ||
readonly?: boolean; | ||
export?: boolean; | ||
@@ -45,0 +44,0 @@ }): ts.EnumDeclaration; |
@@ -130,6 +130,3 @@ import { parseRef } from "@redocly/openapi-core/lib/ref-utils.js"; | ||
return ts.factory.createEnumDeclaration(options | ||
? tsModifiers({ | ||
readonly: options.readonly ?? false, | ||
export: options.export ?? false, | ||
}) | ||
? tsModifiers({ export: options.export ?? false }) | ||
: undefined, enumName, members.map((value, i) => tsEnumMember(value, metadata?.[i]))); | ||
@@ -143,2 +140,5 @@ } | ||
} | ||
else if (name[0] === "-") { | ||
name = `ValueMinus${name.slice(1)}`; | ||
} | ||
name = name.replace(JS_PROPERTY_INDEX_INVALID_CHARS_RE, "_"); | ||
@@ -148,3 +148,6 @@ } | ||
if (typeof value === "number") { | ||
member = ts.factory.createEnumMember(name, ts.factory.createNumericLiteral(value)); | ||
const literal = value < 0 | ||
? ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(Math.abs(value))) | ||
: ts.factory.createNumericLiteral(value); | ||
member = ts.factory.createEnumMember(name, literal); | ||
} | ||
@@ -151,0 +154,0 @@ else { |
@@ -124,2 +124,35 @@ import { escapePointer, parseRef, } from "@redocly/openapi-core/lib/ref-utils.js"; | ||
} | ||
function patchDiscriminatorEnum(schema, ref, values, discriminator, discriminatorRef, options) { | ||
const resolvedSchema = resolveRef(schema, ref, { | ||
silent: options.silent ?? false, | ||
}); | ||
if (resolvedSchema?.allOf) { | ||
resolvedSchema.allOf.push({ | ||
type: "object", | ||
required: [discriminator.propertyName], | ||
properties: { | ||
[discriminator.propertyName]: createDiscriminatorEnum(values), | ||
}, | ||
}); | ||
return true; | ||
} | ||
else if (typeof resolvedSchema === "object" && | ||
"type" in resolvedSchema && | ||
resolvedSchema.type === "object") { | ||
if (!resolvedSchema.properties) { | ||
resolvedSchema.properties = {}; | ||
} | ||
if (!resolvedSchema.required) { | ||
resolvedSchema.required = [discriminator.propertyName]; | ||
} | ||
else if (!resolvedSchema.required.includes(discriminator.propertyName)) { | ||
resolvedSchema.required.push(discriminator.propertyName); | ||
} | ||
resolvedSchema.properties[discriminator.propertyName] = | ||
createDiscriminatorEnum(values, resolvedSchema.properties[discriminator.propertyName]); | ||
return true; | ||
} | ||
warn(`Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${ref} => ${values.join(", ")} (Discriminator: ${discriminatorRef})`, options.silent); | ||
return false; | ||
} | ||
export function scanDiscriminators(schema, options) { | ||
@@ -173,53 +206,38 @@ const objects = {}; | ||
const mappedValues = defined ?? [inferred]; | ||
const resolvedSchema = resolveRef(schema, mappedRef, { | ||
silent: options.silent ?? false, | ||
}); | ||
if (resolvedSchema?.allOf) { | ||
resolvedSchema.allOf.push({ | ||
type: "object", | ||
required: [discriminator.propertyName], | ||
properties: { | ||
[discriminator.propertyName]: createDiscriminatorEnum(mappedValues), | ||
}, | ||
}); | ||
if (patchDiscriminatorEnum(schema, mappedRef, mappedValues, discriminator, ref, options)) { | ||
refsHandled.push(mappedRef); | ||
} | ||
else if (typeof resolvedSchema === "object" && | ||
"type" in resolvedSchema && | ||
resolvedSchema.type === "object") { | ||
if (!resolvedSchema.properties) { | ||
resolvedSchema.properties = {}; | ||
} | ||
if (!resolvedSchema.required) { | ||
resolvedSchema.required = [discriminator.propertyName]; | ||
} | ||
else if (!resolvedSchema.required.includes(discriminator.propertyName)) { | ||
resolvedSchema.required.push(discriminator.propertyName); | ||
} | ||
resolvedSchema.properties[discriminator.propertyName] = | ||
createDiscriminatorEnum(mappedValues, resolvedSchema.properties[discriminator.propertyName]); | ||
refsHandled.push(mappedRef); | ||
} | ||
else { | ||
warn(`Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${mappedRef} => ${mappedValues.join(", ")} (Discriminator: ${ref})`, options.silent); | ||
continue; | ||
} | ||
} | ||
}); | ||
walk(schema, (obj, path) => { | ||
for (const key of ["oneOf", "anyOf", "allOf"]) { | ||
if (obj && Array.isArray(obj[key])) { | ||
for (const item of obj[key]) { | ||
if ("$ref" in item) { | ||
if (objects[item.$ref]) { | ||
objects[createRef(path)] = { | ||
...objects[item.$ref], | ||
}; | ||
if (!obj || !Array.isArray(obj.allOf)) { | ||
return; | ||
} | ||
for (const item of obj.allOf) { | ||
if ("$ref" in item) { | ||
if (!objects[item.$ref]) { | ||
return; | ||
} | ||
const ref = createRef(path); | ||
const discriminator = objects[item.$ref]; | ||
const mappedValues = []; | ||
if (discriminator.mapping) { | ||
for (const mappedValue in discriminator.mapping) { | ||
if (discriminator.mapping[mappedValue] === ref) { | ||
mappedValues.push(mappedValue); | ||
} | ||
} | ||
else if (item.discriminator?.propertyName) { | ||
objects[createRef(path)] = { ...item.discriminator }; | ||
if (mappedValues.length > 0) { | ||
if (patchDiscriminatorEnum(schema, ref, mappedValues, discriminator, item.$ref, options)) { | ||
refsHandled.push(ref); | ||
} | ||
} | ||
} | ||
objects[ref] = { | ||
...objects[item.$ref], | ||
}; | ||
} | ||
else if (item.discriminator?.propertyName) { | ||
objects[createRef(path)] = { ...item.discriminator }; | ||
} | ||
} | ||
@@ -226,0 +244,0 @@ }); |
@@ -45,3 +45,5 @@ import { parseRef } from "@redocly/openapi-core/lib/ref-utils.js"; | ||
})); | ||
const enumType = tsEnum(enumName, schemaObject.enum, metadata, { export: true, readonly: options.ctx.immutable }); | ||
const enumType = tsEnum(enumName, schemaObject.enum, metadata, { | ||
export: true, | ||
}); | ||
options.ctx.injectFooter.push(enumType); | ||
@@ -52,5 +54,12 @@ return ts.factory.createTypeReferenceNode(enumType.name); | ||
} | ||
function collectCompositions(items, required) { | ||
function collectUnionCompositions(items) { | ||
const output = []; | ||
for (const item of items) { | ||
output.push(transformSchemaObject(item, options)); | ||
} | ||
return output; | ||
} | ||
function collectAllOfCompositions(items, required) { | ||
const output = []; | ||
for (const item of items) { | ||
let itemType; | ||
@@ -62,8 +71,7 @@ if ("$ref" in item) { | ||
typeof resolved === "object" && | ||
"properties" in resolved) { | ||
if (!options.ctx.discriminators.refsHandled.includes(item.$ref)) { | ||
const validRequired = (required ?? []).filter((key) => !!resolved.properties[key]); | ||
if (validRequired.length) { | ||
itemType = tsWithRequired(itemType, validRequired, options.ctx.injectFooter); | ||
} | ||
"properties" in resolved && | ||
!options.ctx.discriminators.refsHandled.includes(item.$ref)) { | ||
const validRequired = (required ?? []).filter((key) => !!resolved.properties[key]); | ||
if (validRequired.length) { | ||
itemType = tsWithRequired(itemType, validRequired, options.ctx.injectFooter); | ||
} | ||
@@ -92,3 +100,3 @@ } | ||
const coreObjectType = transformSchemaObjectCore(schemaObject, options); | ||
const allOfType = collectCompositions(schemaObject.allOf ?? [], schemaObject.required); | ||
const allOfType = collectAllOfCompositions(schemaObject.allOf ?? [], schemaObject.required); | ||
if (coreObjectType || allOfType.length) { | ||
@@ -103,11 +111,11 @@ const allOf = allOfType.length | ||
} | ||
const anyOfType = collectCompositions(schemaObject.anyOf ?? [], schemaObject.required); | ||
const anyOfType = collectUnionCompositions(schemaObject.anyOf ?? []); | ||
if (anyOfType.length) { | ||
finalType = tsUnion([...(finalType ? [finalType] : []), ...anyOfType]); | ||
} | ||
const oneOfType = collectCompositions(schemaObject.oneOf || | ||
const oneOfType = collectUnionCompositions(schemaObject.oneOf || | ||
("type" in schemaObject && | ||
schemaObject.type === "object" && | ||
schemaObject.enum) || | ||
[], schemaObject.required); | ||
[]); | ||
if (oneOfType.length) { | ||
@@ -229,3 +237,3 @@ if (oneOfType.every(tsIsPrimitive)) { | ||
const coreObjectType = []; | ||
for (const k of ["oneOf", "allOf", "anyOf"]) { | ||
for (const k of ["allOf", "anyOf"]) { | ||
if (!schemaObject[k]) { | ||
@@ -235,2 +243,3 @@ continue; | ||
const discriminator = !schemaObject.discriminator && | ||
!options.ctx.discriminators.refsHandled.includes(options.path) && | ||
options.ctx.discriminators.objects[options.path]; | ||
@@ -237,0 +246,0 @@ if (discriminator) { |
{ | ||
"name": "openapi-typescript", | ||
"description": "Convert OpenAPI 3.0 & 3.1 schemas to TypeScript", | ||
"version": "7.0.0-next.8", | ||
"version": "7.0.0-next.9", | ||
"author": { | ||
@@ -65,3 +65,3 @@ "name": "Drew Powers", | ||
"dependencies": { | ||
"@redocly/openapi-core": "^1.10.3", | ||
"@redocly/openapi-core": "^1.11.0", | ||
"ansi-colors": "^4.1.3", | ||
@@ -74,11 +74,11 @@ "supports-color": "^9.4.0", | ||
"@types/js-yaml": "^4.0.9", | ||
"@types/node": "^20.11.24", | ||
"@types/node": "^20.12.7", | ||
"degit": "^2.8.4", | ||
"del-cli": "^5.1.0", | ||
"esbuild": "^0.20.1", | ||
"esbuild": "^0.20.2", | ||
"execa": "^7.2.0", | ||
"typescript": "^5.3.3", | ||
"vite-node": "^1.3.1", | ||
"vitest": "^1.3.1" | ||
"typescript": "^5.4.5", | ||
"vite-node": "^1.5.0", | ||
"vitest": "^1.5.0" | ||
} | ||
} |
@@ -233,3 +233,3 @@ import { parseRef } from "@redocly/openapi-core/lib/ref-utils.js"; | ||
metadata?: { name?: string; description?: string }[], | ||
options?: { readonly?: boolean; export?: boolean }, | ||
options?: { export?: boolean }, | ||
) { | ||
@@ -248,6 +248,3 @@ let enumName = name.replace(JS_ENUM_INVALID_CHARS_RE, (c) => { | ||
/* modifiers */ options | ||
? tsModifiers({ | ||
readonly: options.readonly ?? false, | ||
export: options.export ?? false, | ||
}) | ||
? tsModifiers({ export: options.export ?? false }) | ||
: undefined, | ||
@@ -270,2 +267,4 @@ /* name */ enumName, | ||
name = `Value${name}`.replace(".", "_"); // don't forged decimals; | ||
} else if (name[0] === "-") { | ||
name = `ValueMinus${name.slice(1)}`; | ||
} | ||
@@ -277,6 +276,11 @@ name = name.replace(JS_PROPERTY_INDEX_INVALID_CHARS_RE, "_"); | ||
if (typeof value === "number") { | ||
member = ts.factory.createEnumMember( | ||
name, | ||
ts.factory.createNumericLiteral(value), | ||
); | ||
const literal = | ||
value < 0 | ||
? ts.factory.createPrefixUnaryExpression( | ||
ts.SyntaxKind.MinusToken, | ||
ts.factory.createNumericLiteral(Math.abs(value)), | ||
) | ||
: ts.factory.createNumericLiteral(value); | ||
member = ts.factory.createEnumMember(name, literal); | ||
} else { | ||
@@ -283,0 +287,0 @@ member = ts.factory.createEnumMember( |
@@ -187,2 +187,64 @@ import { | ||
/** Adds or replaces the discriminator enum with the passed `values` in a schema defined by `ref` */ | ||
function patchDiscriminatorEnum( | ||
schema: SchemaObject, | ||
ref: string, | ||
values: string[], | ||
discriminator: DiscriminatorObject, | ||
discriminatorRef: string, | ||
options: OpenAPITSOptions, | ||
): boolean { | ||
const resolvedSchema = resolveRef<SchemaObject>(schema, ref, { | ||
silent: options.silent ?? false, | ||
}); | ||
if (resolvedSchema?.allOf) { | ||
// if the schema is an allOf, we can append a new schema object to the allOf array | ||
resolvedSchema.allOf.push({ | ||
type: "object", | ||
// discriminator enum properties always need to be required | ||
required: [discriminator.propertyName], | ||
properties: { | ||
[discriminator.propertyName]: createDiscriminatorEnum(values), | ||
}, | ||
}); | ||
return true; | ||
} else if ( | ||
typeof resolvedSchema === "object" && | ||
"type" in resolvedSchema && | ||
resolvedSchema.type === "object" | ||
) { | ||
// if the schema is an object, we can apply the discriminator enums to its properties | ||
if (!resolvedSchema.properties) { | ||
resolvedSchema.properties = {}; | ||
} | ||
// discriminator enum properties always need to be required | ||
if (!resolvedSchema.required) { | ||
resolvedSchema.required = [discriminator.propertyName]; | ||
} else if (!resolvedSchema.required.includes(discriminator.propertyName)) { | ||
resolvedSchema.required.push(discriminator.propertyName); | ||
} | ||
// add/replace the discriminator enum property | ||
resolvedSchema.properties[discriminator.propertyName] = | ||
createDiscriminatorEnum( | ||
values, | ||
resolvedSchema.properties[discriminator.propertyName], | ||
); | ||
return true; | ||
} | ||
warn( | ||
`Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${ref} => ${values.join( | ||
", ", | ||
)} (Discriminator: ${discriminatorRef})`, | ||
options.silent, | ||
); | ||
return false; | ||
} | ||
type InternalDiscriminatorMapping = Record< | ||
@@ -271,53 +333,14 @@ string, | ||
const mappedValues = defined ?? [inferred!]; | ||
const resolvedSchema = resolveRef<SchemaObject>(schema, mappedRef, { | ||
silent: options.silent ?? false, | ||
}); | ||
if (resolvedSchema?.allOf) { | ||
// if the schema is an allOf, we can append a new schema object to the allOf array | ||
resolvedSchema.allOf.push({ | ||
type: "object", | ||
// discriminator enum properties always need to be required | ||
required: [discriminator.propertyName], | ||
properties: { | ||
[discriminator.propertyName]: createDiscriminatorEnum(mappedValues), | ||
}, | ||
}); | ||
refsHandled.push(mappedRef); | ||
} else if ( | ||
typeof resolvedSchema === "object" && | ||
"type" in resolvedSchema && | ||
resolvedSchema.type === "object" | ||
if ( | ||
patchDiscriminatorEnum( | ||
schema, | ||
mappedRef, | ||
mappedValues, | ||
discriminator, | ||
ref, | ||
options, | ||
) | ||
) { | ||
// if the schema is an object, we can apply the discriminator enums to its properties | ||
if (!resolvedSchema.properties) { | ||
resolvedSchema.properties = {}; | ||
} | ||
// discriminator enum properties always need to be required | ||
if (!resolvedSchema.required) { | ||
resolvedSchema.required = [discriminator.propertyName]; | ||
} else if ( | ||
!resolvedSchema.required.includes(discriminator.propertyName) | ||
) { | ||
resolvedSchema.required.push(discriminator.propertyName); | ||
} | ||
// add/replace the discriminator enum property | ||
resolvedSchema.properties[discriminator.propertyName] = | ||
createDiscriminatorEnum( | ||
mappedValues, | ||
resolvedSchema.properties[discriminator.propertyName], | ||
); | ||
refsHandled.push(mappedRef); | ||
} else { | ||
warn( | ||
`Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${mappedRef} => ${mappedValues.join( | ||
", ", | ||
)} (Discriminator: ${ref})`, | ||
options.silent, | ||
); | ||
continue; | ||
} | ||
@@ -331,15 +354,44 @@ } | ||
walk(schema, (obj, path) => { | ||
for (const key of ["oneOf", "anyOf", "allOf"] as const) { | ||
if (obj && Array.isArray(obj[key])) { | ||
for (const item of (obj as any)[key]) { | ||
if ("$ref" in item) { | ||
if (objects[item.$ref]) { | ||
objects[createRef(path)] = { | ||
...objects[item.$ref], | ||
}; | ||
if (!obj || !Array.isArray(obj.allOf)) { | ||
return; | ||
} | ||
for (const item of (obj as any).allOf) { | ||
if ("$ref" in item) { | ||
if (!objects[item.$ref]) { | ||
return; | ||
} | ||
const ref = createRef(path); | ||
const discriminator = objects[item.$ref]; | ||
const mappedValues: string[] = []; | ||
if (discriminator.mapping) { | ||
for (const mappedValue in discriminator.mapping) { | ||
if (discriminator.mapping[mappedValue] === ref) { | ||
mappedValues.push(mappedValue); | ||
} | ||
} else if (item.discriminator?.propertyName) { | ||
objects[createRef(path)] = { ...item.discriminator }; | ||
} | ||
if (mappedValues.length > 0) { | ||
if ( | ||
patchDiscriminatorEnum( | ||
schema, | ||
ref, | ||
mappedValues, | ||
discriminator, | ||
item.$ref, | ||
options, | ||
) | ||
) { | ||
refsHandled.push(ref); | ||
} | ||
} | ||
} | ||
objects[ref] = { | ||
...objects[item.$ref], | ||
}; | ||
} else if (item.discriminator?.propertyName) { | ||
objects[createRef(path)] = { ...item.discriminator }; | ||
} | ||
@@ -346,0 +398,0 @@ } |
@@ -129,3 +129,7 @@ import { parseRef } from "@redocly/openapi-core/lib/ref-utils.js"; | ||
metadata, | ||
{ export: true, readonly: options.ctx.immutable }, | ||
{ | ||
export: true, | ||
// readonly: TS enum do not support the readonly modifier | ||
}, | ||
); | ||
@@ -142,4 +146,14 @@ options.ctx.injectFooter.push(enumType); | ||
/** Collect oneOf/allOf/anyOf with Omit<> for discriminators */ | ||
function collectCompositions( | ||
/** Collect oneOf/anyOf */ | ||
function collectUnionCompositions(items: (SchemaObject | ReferenceObject)[]) { | ||
const output: ts.TypeNode[] = []; | ||
for (const item of items) { | ||
output.push(transformSchemaObject(item, options)); | ||
} | ||
return output; | ||
} | ||
/** Collect allOf with Omit<> for discriminators */ | ||
function collectAllOfCompositions( | ||
items: (SchemaObject | ReferenceObject)[], | ||
@@ -157,20 +171,21 @@ required?: string[], | ||
const resolved = options.ctx.resolve<SchemaObject>(item.$ref); | ||
// make keys required, if necessary | ||
if ( | ||
resolved && | ||
typeof resolved === "object" && | ||
"properties" in resolved | ||
"properties" in resolved && | ||
// we have already handled this item (discriminator property was already added as required) | ||
!options.ctx.discriminators.refsHandled.includes(item.$ref) | ||
) { | ||
// don’t try and make keys required if we have already handled the item (discriminator property was already added as required) | ||
// or the $ref doesn’t have them | ||
if (!options.ctx.discriminators.refsHandled.includes(item.$ref)) { | ||
const validRequired = (required ?? []).filter( | ||
(key) => !!resolved.properties![key], | ||
// add WithRequired<X, Y> if necessary | ||
const validRequired = (required ?? []).filter( | ||
(key) => !!resolved.properties![key], | ||
); | ||
if (validRequired.length) { | ||
itemType = tsWithRequired( | ||
itemType, | ||
validRequired, | ||
options.ctx.injectFooter, | ||
); | ||
if (validRequired.length) { | ||
itemType = tsWithRequired( | ||
itemType, | ||
validRequired, | ||
options.ctx.injectFooter, | ||
); | ||
} | ||
} | ||
@@ -190,2 +205,3 @@ } | ||
} | ||
const discriminator = | ||
@@ -208,3 +224,3 @@ ("$ref" in item && options.ctx.discriminators.objects[item.$ref]) || | ||
const coreObjectType = transformSchemaObjectCore(schemaObject, options); | ||
const allOfType = collectCompositions( | ||
const allOfType = collectAllOfCompositions( | ||
schemaObject.allOf ?? [], | ||
@@ -224,6 +240,3 @@ schemaObject.required, | ||
// (note: this may seem counterintuitive, but as TypeScript’s unions are not true XORs, they mimic behavior closer to anyOf than oneOf) | ||
const anyOfType = collectCompositions( | ||
schemaObject.anyOf ?? [], | ||
schemaObject.required, | ||
); | ||
const anyOfType = collectUnionCompositions(schemaObject.anyOf ?? []); | ||
if (anyOfType.length) { | ||
@@ -233,3 +246,3 @@ finalType = tsUnion([...(finalType ? [finalType] : []), ...anyOfType]); | ||
// oneOf: union (within intersection with other types, if any) | ||
const oneOfType = collectCompositions( | ||
const oneOfType = collectUnionCompositions( | ||
schemaObject.oneOf || | ||
@@ -240,3 +253,2 @@ ("type" in schemaObject && | ||
[], | ||
schemaObject.required, | ||
); | ||
@@ -419,4 +431,4 @@ if (oneOfType.length) { | ||
// discriminatorss: explicit mapping on schema object | ||
for (const k of ["oneOf", "allOf", "anyOf"] as const) { | ||
// discriminators: explicit mapping on schema object | ||
for (const k of ["allOf", "anyOf"] as const) { | ||
if (!schemaObject[k]) { | ||
@@ -428,4 +440,7 @@ continue; | ||
// discriminator meant for children (!schemaObject.discriminator) | ||
// and don't add discriminator properties if we already added/patched | ||
// them (options.ctx.discriminators.refsHandled.includes(options.path!). | ||
const discriminator = | ||
!schemaObject.discriminator && | ||
!options.ctx.discriminators.refsHandled.includes(options.path!) && | ||
options.ctx.discriminators.objects[options.path!]; | ||
@@ -432,0 +447,0 @@ if (discriminator) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
433027
8217
+ Added@redocly/config@0.20.3(transitive)
+ Added@redocly/openapi-core@1.28.0(transitive)
- Removed@redocly/config@0.20.2(transitive)
- Removed@redocly/openapi-core@1.27.2(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removedtr46@0.0.3(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)