UNPKG

@kubb/plugin-oas

Version:
773 lines (768 loc) • 26.1 kB
'use strict'; var chunkB7KP5ZFA_cjs = require('./chunk-B7KP5ZFA.cjs'); var core = require('@kubb/core'); var transformers = require('@kubb/core/transformers'); var utils = require('@kubb/core/utils'); var oas = require('@kubb/oas'); var remeda = require('remeda'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var transformers__default = /*#__PURE__*/_interopDefault(transformers); // src/SchemaMapper.ts var schemaKeywords = { any: "any", unknown: "unknown", number: "number", integer: "integer", string: "string", boolean: "boolean", undefined: "undefined", nullable: "nullable", null: "null", nullish: "nullish", array: "array", tuple: "tuple", enum: "enum", union: "union", datetime: "datetime", date: "date", email: "email", uuid: "uuid", url: "url", void: "void", /* intersection */ default: "default", const: "const", and: "and", describe: "describe", min: "min", max: "max", optional: "optional", readOnly: "readOnly", writeOnly: "writeOnly", // custom ones object: "object", ref: "ref", matches: "matches", firstName: "firstName", lastName: "lastName", password: "password", phone: "phone", blob: "blob", deprecated: "deprecated", example: "example", schema: "schema", catchall: "catchall", time: "time", name: "name", interface: "interface" }; function isKeyword(meta, keyword) { return meta.keyword === keyword; } var SchemaGenerator = class _SchemaGenerator extends core.BaseGenerator { // Collect the types of all referenced schemas, so we can export them later refs = {}; // Keep track of already used type aliases #usedAliasNames = {}; /** * Creates a type node from a given schema. * Delegates to getBaseTypeFromSchema internally and * optionally adds a union with null. */ parse(props) { const options = this.#getOptions(props); const defaultSchemas = this.#parseSchemaObject(props); const schemas = options.transformers?.schema?.(props, defaultSchemas) || defaultSchemas || []; return remeda.uniqueWith(schemas, remeda.isDeepEqual); } deepSearch(tree, keyword) { return _SchemaGenerator.deepSearch(tree, keyword); } find(tree, keyword) { return _SchemaGenerator.find(tree, keyword); } static deepSearch(tree, keyword) { const foundItems = []; tree?.forEach((schema) => { if (schema.keyword === keyword) { foundItems.push(schema); } if (isKeyword(schema, schemaKeywords.object)) { Object.values(schema.args?.properties || {}).forEach((entrySchema) => { foundItems.push(..._SchemaGenerator.deepSearch(entrySchema, keyword)); }); Object.values(schema.args?.additionalProperties || {}).forEach((entrySchema) => { foundItems.push(..._SchemaGenerator.deepSearch([entrySchema], keyword)); }); } if (isKeyword(schema, schemaKeywords.array)) { schema.args.items.forEach((entrySchema) => { foundItems.push(..._SchemaGenerator.deepSearch([entrySchema], keyword)); }); } if (isKeyword(schema, schemaKeywords.and)) { schema.args.forEach((entrySchema) => { foundItems.push(..._SchemaGenerator.deepSearch([entrySchema], keyword)); }); } if (isKeyword(schema, schemaKeywords.tuple)) { schema.args.items.forEach((entrySchema) => { foundItems.push(..._SchemaGenerator.deepSearch([entrySchema], keyword)); }); } if (isKeyword(schema, schemaKeywords.union)) { schema.args.forEach((entrySchema) => { foundItems.push(..._SchemaGenerator.deepSearch([entrySchema], keyword)); }); } }); return foundItems; } static findInObject(tree, keyword) { let foundItem = void 0; tree?.forEach((schema) => { if (!foundItem && schema.keyword === keyword) { foundItem = schema; } if (isKeyword(schema, schemaKeywords.object)) { Object.values(schema.args?.properties || {}).forEach((entrySchema) => { if (!foundItem) { foundItem = _SchemaGenerator.find(entrySchema, keyword); } }); Object.values(schema.args?.additionalProperties || {}).forEach((entrySchema) => { if (!foundItem) { foundItem = _SchemaGenerator.find([entrySchema], keyword); } }); } }); return foundItem; } static find(tree, keyword) { let foundItem = void 0; tree?.forEach((schema) => { if (!foundItem && schema.keyword === keyword) { foundItem = schema; } if (isKeyword(schema, schemaKeywords.array)) { schema.args.items.forEach((entrySchema) => { if (!foundItem) { foundItem = _SchemaGenerator.find([entrySchema], keyword); } }); } if (isKeyword(schema, schemaKeywords.and)) { schema.args.forEach((entrySchema) => { if (!foundItem) { foundItem = _SchemaGenerator.find([entrySchema], keyword); } }); } if (isKeyword(schema, schemaKeywords.tuple)) { schema.args.items.forEach((entrySchema) => { if (!foundItem) { foundItem = _SchemaGenerator.find([entrySchema], keyword); } }); } if (isKeyword(schema, schemaKeywords.union)) { schema.args.forEach((entrySchema) => { if (!foundItem) { foundItem = _SchemaGenerator.find([entrySchema], keyword); } }); } }); return foundItem; } #getUsedEnumNames(props) { const options = this.#getOptions(props); return options.usedEnumNames || {}; } #getOptions({ name }) { const { override = [] } = this.context; return { ...this.options, ...override.find(({ pattern, type }) => { if (name && type === "schemaName") { return !!name.match(pattern); } return false; })?.options || {} }; } #getUnknownReturn(props) { const options = this.#getOptions(props); if (options.unknownType === "any") { return schemaKeywords.any; } if (options.unknownType === "void") { return schemaKeywords.void; } return schemaKeywords.unknown; } /** * Recursively creates a type literal with the given props. */ #parseProperties({ schema, name }) { const properties = schema?.properties || {}; const additionalProperties = schema?.additionalProperties; const required = schema?.required; const propertiesSchemas = Object.keys(properties).map((propertyName) => { const validationFunctions = []; const propertySchema = properties[propertyName]; const isRequired = Array.isArray(required) ? required?.includes(propertyName) : !!required; const nullable = propertySchema.nullable ?? propertySchema["x-nullable"] ?? false; validationFunctions.push(...this.parse({ schema: propertySchema, name: propertyName, parentName: name })); validationFunctions.push({ keyword: schemaKeywords.name, args: propertyName }); if (!isRequired && nullable) { validationFunctions.push({ keyword: schemaKeywords.nullish }); } else if (!isRequired) { validationFunctions.push({ keyword: schemaKeywords.optional }); } return { [propertyName]: validationFunctions }; }).reduce((acc, curr) => ({ ...acc, ...curr }), {}); let additionalPropertiesSchemas = []; if (additionalProperties) { additionalPropertiesSchemas = additionalProperties === true || !Object.keys(additionalProperties).length ? [{ keyword: this.#getUnknownReturn({ schema, name }) }] : this.parse({ schema: additionalProperties, parentName: name }); } return [ { keyword: schemaKeywords.object, args: { properties: propertiesSchemas, additionalProperties: additionalPropertiesSchemas } } ]; } /** * Create a type alias for the schema referenced by the given ReferenceObject */ #getRefAlias(obj) { const { $ref } = obj; let ref = this.refs[$ref]; const originalName = utils.getUniqueName($ref.replace(/.+\//, ""), this.#usedAliasNames); const propertyName = this.context.pluginManager.resolveName({ name: originalName, pluginKey: this.context.plugin.key, type: "function" }); if (ref) { return [ { keyword: schemaKeywords.ref, args: { name: ref.propertyName, path: ref.path, isImportable: !!this.context.oas.get($ref) } } ]; } const fileName = this.context.pluginManager.resolveName({ name: originalName, pluginKey: this.context.plugin.key, type: "file" }); const file = this.context.pluginManager.getFile({ name: fileName, pluginKey: this.context.plugin.key, extname: ".ts" }); ref = this.refs[$ref] = { propertyName, originalName, path: file.path }; return [ { keyword: schemaKeywords.ref, args: { name: ref.propertyName, path: ref?.path, isImportable: !!this.context.oas.get($ref) } } ]; } #getParsedSchemaObject(schema) { const parsedSchema = chunkB7KP5ZFA_cjs.getSchemaFactory(this.context.oas)(schema); return parsedSchema; } /** * This is the very core of the OpenAPI to TS conversion - it takes a * schema and returns the appropriate type. */ #parseSchemaObject({ schema: _schema, name, parentName }) { const options = this.#getOptions({ schema: _schema, name }); const unknownReturn = this.#getUnknownReturn({ schema: _schema, name }); const { schema, version } = this.#getParsedSchemaObject(_schema); if (!schema) { return [{ keyword: unknownReturn }]; } const baseItems = [ { keyword: schemaKeywords.schema, args: { type: schema.type, format: schema.format } } ]; const min = schema.minimum ?? schema.minLength ?? schema.minItems ?? void 0; const max = schema.maximum ?? schema.maxLength ?? schema.maxItems ?? void 0; const nullable = oas.isNullable(schema); const defaultNullAndNullable = schema.default === null && nullable; if (schema.default !== void 0 && !defaultNullAndNullable && !Array.isArray(schema.default)) { if (typeof schema.default === "string") { baseItems.push({ keyword: schemaKeywords.default, args: transformers__default.default.stringify(schema.default) }); } else if (typeof schema.default === "boolean") { baseItems.push({ keyword: schemaKeywords.default, args: schema.default ?? false }); } else { baseItems.push({ keyword: schemaKeywords.default, args: schema.default }); } } if (schema.deprecated) { baseItems.push({ keyword: schemaKeywords.deprecated }); } if (schema.description) { baseItems.push({ keyword: schemaKeywords.describe, args: schema.description }); } if (max !== void 0) { baseItems.unshift({ keyword: schemaKeywords.max, args: max }); } if (min !== void 0) { baseItems.unshift({ keyword: schemaKeywords.min, args: min }); } if (nullable) { baseItems.push({ keyword: schemaKeywords.nullable }); } if (schema.type && Array.isArray(schema.type)) { const [_schema2, nullable2] = schema.type; if (nullable2 === "null") { baseItems.push({ keyword: schemaKeywords.nullable }); } } if (schema.readOnly) { baseItems.push({ keyword: schemaKeywords.readOnly }); } if (schema.writeOnly) { baseItems.push({ keyword: schemaKeywords.writeOnly }); } if (oas.isReference(schema)) { return [ ...this.#getRefAlias(schema), nullable && { keyword: schemaKeywords.nullable }, schema.readOnly && { keyword: schemaKeywords.readOnly }, schema.writeOnly && { keyword: schemaKeywords.writeOnly }, { keyword: schemaKeywords.schema, args: { type: schema.type, format: schema.format } } ].filter(Boolean); } if (schema.oneOf) { const schemaWithoutOneOf = { ...schema, oneOf: void 0 }; const union = { keyword: schemaKeywords.union, args: schema.oneOf.map((item) => { return item && this.parse({ schema: item, name, parentName })[0]; }).filter(Boolean).filter((item) => { return item && item.keyword !== unknownReturn; }) }; if (schemaWithoutOneOf.properties) { const propertySchemas = this.parse({ schema: schemaWithoutOneOf, name, parentName }); union.args = [ ...union.args.map((arg) => { return { keyword: schemaKeywords.and, args: [arg, ...propertySchemas] }; }) ]; } return [union, ...baseItems]; } if (schema.anyOf) { const schemaWithoutAnyOf = { ...schema, anyOf: void 0 }; const union = { keyword: schemaKeywords.union, args: schema.anyOf.map((item) => { return item && this.parse({ schema: item, name, parentName })[0]; }).filter(Boolean).filter((item) => { return item && item.keyword !== unknownReturn; }).map((item) => { if (isKeyword(item, schemaKeywords.object)) { return { ...item, args: { ...item.args, strict: true } }; } return item; }) }; if (schemaWithoutAnyOf.properties) { return [...this.parse({ schema: schemaWithoutAnyOf, name, parentName }), union, ...baseItems]; } return [union, ...baseItems]; } if (schema.allOf) { const schemaWithoutAllOf = { ...schema, allOf: void 0 }; const and = { keyword: schemaKeywords.and, args: schema.allOf.map((item) => { return item && this.parse({ schema: item, name, parentName })[0]; }).filter(Boolean).filter((item) => { return item && item.keyword !== unknownReturn; }) }; if (schemaWithoutAllOf.required) { const schemas = schema.allOf.map((item) => { if (oas.isReference(item)) { return this.context.oas.get(item.$ref); } }).filter(Boolean); const items = schemaWithoutAllOf.required.filter((key) => { if (schemaWithoutAllOf.properties) { return !Object.keys(schemaWithoutAllOf.properties).includes(key); } return true; }).map((key) => { const schema2 = schemas.find((item) => item.properties && Object.keys(item.properties).find((propertyKey) => propertyKey === key)); if (schema2?.properties?.[key]) { return { ...schema2, properties: { [key]: schema2.properties[key] }, required: [key] }; } }).filter(Boolean); and.args = [...and.args || [], ...items.flatMap((item) => this.parse({ schema: item, name, parentName }))]; } if (schemaWithoutAllOf.properties) { and.args = [...and.args || [], ...this.parse({ schema: schemaWithoutAllOf, name, parentName })]; } return [and, ...baseItems]; } if (schema.enum) { if (options.enumSuffix === "") { throw new Error("EnumSuffix set to an empty string does not work"); } const enumName = utils.getUniqueName(transformers.pascalCase([parentName, name, options.enumSuffix].join(" ")), this.#getUsedEnumNames({ schema, name })); const typeName = this.context.pluginManager.resolveName({ name: enumName, pluginKey: this.context.plugin.key, type: "type" }); const nullableEnum = schema.enum.includes(null); if (nullableEnum) { baseItems.push({ keyword: schemaKeywords.nullable }); } const filteredValues = schema.enum.filter((value) => value !== null); const extensionEnums = ["x-enumNames", "x-enum-varnames"].filter((extensionKey) => extensionKey in schema).map((extensionKey) => { return [ { keyword: schemaKeywords.enum, args: { name, typeName, asConst: false, items: [...new Set(schema[extensionKey])].map((name2, index) => ({ name: transformers__default.default.stringify(name2), value: schema.enum?.[index], format: remeda.isNumber(schema.enum?.[index]) ? "number" : "string" })) } }, ...baseItems.filter( (item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max && item.keyword !== schemaKeywords.matches ) ]; }); if (schema.type === "number" || schema.type === "integer") { const enumNames = extensionEnums[0]?.find((item) => isKeyword(item, schemaKeywords.enum)); return [ { keyword: schemaKeywords.enum, args: { name: enumName, typeName, asConst: true, items: enumNames?.args?.items ? [...new Set(enumNames.args.items)].map(({ name: name2, value }) => ({ name: name2, value, format: "number" })) : [...new Set(filteredValues)].map((value) => { return { name: value, value, format: "number" }; }) } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max && item.keyword !== schemaKeywords.matches) ]; } if (schema.type === "boolean") { const enumNames = extensionEnums[0]?.find((item) => isKeyword(item, schemaKeywords.enum)); return [ { keyword: schemaKeywords.enum, args: { name: enumName, typeName, asConst: true, items: enumNames?.args?.items ? [...new Set(enumNames.args.items)].map(({ name: name2, value }) => ({ name: name2, value, format: "boolean" })) : [...new Set(filteredValues)].map((value) => { return { name: value, value, format: "boolean" }; }) } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.matches) ]; } if (extensionEnums.length > 0 && extensionEnums[0]) { return extensionEnums[0]; } return [ { keyword: schemaKeywords.enum, args: { name: enumName, typeName, asConst: false, items: [...new Set(filteredValues)].map((value) => ({ name: transformers__default.default.stringify(value), value, format: remeda.isNumber(value) ? "number" : "string" })) } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max && item.keyword !== schemaKeywords.matches) ]; } if ("prefixItems" in schema) { const prefixItems = schema.prefixItems; const min2 = schema.minimum ?? schema.minLength ?? schema.minItems ?? void 0; const max2 = schema.maximum ?? schema.maxLength ?? schema.maxItems ?? void 0; return [ { keyword: schemaKeywords.tuple, args: { min: min2, max: max2, items: prefixItems.map((item) => { return this.parse({ schema: item, name, parentName })[0]; }).filter(Boolean) } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max) ]; } if (version === "3.1" && "const" in schema) { if (schema["const"]) { return [ { keyword: schemaKeywords.const, args: { name: schema["const"], format: typeof schema["const"] === "number" ? "number" : "string", value: schema["const"] } }, ...baseItems ]; } return [{ keyword: schemaKeywords.null }]; } if (schema.format) { switch (schema.format) { case "binary": baseItems.push({ keyword: schemaKeywords.blob }); return baseItems; case "date-time": if (options.dateType) { if (options.dateType === "date") { baseItems.unshift({ keyword: schemaKeywords.date, args: { type: "date" } }); return baseItems; } if (options.dateType === "stringOffset") { baseItems.unshift({ keyword: schemaKeywords.datetime, args: { offset: true } }); return baseItems; } if (options.dateType === "stringLocal") { baseItems.unshift({ keyword: schemaKeywords.datetime, args: { local: true } }); return baseItems; } baseItems.unshift({ keyword: schemaKeywords.datetime, args: { offset: false } }); return baseItems; } break; case "date": if (options.dateType) { if (options.dateType === "date") { baseItems.unshift({ keyword: schemaKeywords.date, args: { type: "date" } }); return baseItems; } baseItems.unshift({ keyword: schemaKeywords.date, args: { type: "string" } }); return baseItems; } break; case "time": if (options.dateType) { if (options.dateType === "date") { baseItems.unshift({ keyword: schemaKeywords.time, args: { type: "date" } }); return baseItems; } baseItems.unshift({ keyword: schemaKeywords.time, args: { type: "string" } }); return baseItems; } break; case "uuid": baseItems.unshift({ keyword: schemaKeywords.uuid }); return baseItems; case "email": case "idn-email": baseItems.unshift({ keyword: schemaKeywords.email }); return baseItems; case "uri": case "ipv4": case "ipv6": case "uri-reference": case "hostname": case "idn-hostname": baseItems.unshift({ keyword: schemaKeywords.url }); return baseItems; } } if (schema.pattern) { baseItems.unshift({ keyword: schemaKeywords.matches, args: schema.pattern }); return baseItems; } if ("items" in schema || schema.type === "array") { const min2 = schema.minimum ?? schema.minLength ?? schema.minItems ?? void 0; const max2 = schema.maximum ?? schema.maxLength ?? schema.maxItems ?? void 0; const items = this.parse({ schema: "items" in schema ? schema.items : [], name, parentName }); const unique = !!schema.uniqueItems; return [ { keyword: schemaKeywords.array, args: { items, min: min2, max: max2, unique } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max) ]; } if (schema.properties || schema.additionalProperties) { return [...this.#parseProperties({ schema, name }), ...baseItems]; } if (schema.type) { if (Array.isArray(schema.type)) { const [type] = schema.type; return [ ...this.parse({ schema: { ...schema, type }, name, parentName }), ...baseItems ].filter(Boolean); } if (!["boolean", "object", "number", "string", "integer", "null"].includes(schema.type)) { this.context.pluginManager.logger.emit("warning", `Schema type '${schema.type}' is not valid for schema ${parentName}.${name}`); } return [{ keyword: schema.type }, ...baseItems]; } return [{ keyword: unknownReturn }]; } async build(...generators) { const { oas, contentType, include } = this.context; oas.resolveDiscriminators(); const schemas = chunkB7KP5ZFA_cjs.getSchemas({ oas, contentType, includes: include }); const promises = Object.entries(schemas).reduce((acc, [name, value]) => { if (!value) { return acc; } const options = this.#getOptions({ name }); const promiseOperation = this.schema.call(this, name, value, { ...this.options, ...options }); if (promiseOperation) { acc.push(promiseOperation); } generators?.forEach((generator) => { const tree = this.parse({ schema: value, name }); const promise = generator.schema?.({ instance: this, schema: { name, value, tree }, options: { ...this.options, ...options } }); if (promise) { acc.push(promise); } }); return acc; }, []); const files = await Promise.all(promises); return files.flat().filter(Boolean); } /** * Schema */ async schema(_name, _object, _options) { return []; } }; exports.SchemaGenerator = SchemaGenerator; exports.isKeyword = isKeyword; exports.schemaKeywords = schemaKeywords; //# sourceMappingURL=chunk-I2LBG5AS.cjs.map //# sourceMappingURL=chunk-I2LBG5AS.cjs.map