UNPKG

@kubb/plugin-oas

Version:

OpenAPI Specification (OAS) plugin for Kubb, providing core functionality for parsing and processing OpenAPI/Swagger schemas for code generation.

1,336 lines (1,335 loc) 44 kB
import "./chunk--u3MIqq1.js"; import { i as pascalCase } from "./getFooter-Pw3tLCiV.js"; import { n as schemaKeywords, t as isKeyword } from "./SchemaMapper-CqMkO2T1.js"; import { KUBB_INLINE_REF_PREFIX, isDiscriminator, isNullable, isOpenApiV3_1Document, isReference } from "@kubb/oas"; import { isDeepEqual, isNumber, uniqueWith } from "remeda"; import { Fabric, createReactFabric } from "@kubb/react-fabric"; import { jsx } from "@kubb/react-fabric/jsx-runtime"; //#region ../../internals/utils/src/names.ts /** * Returns a unique name by appending an incrementing numeric suffix when the name has already been used. * Mutates `data` in-place as a usage counter so subsequent calls remain consistent. * * @example * const seen: Record<string, number> = {} * getUniqueName('Foo', seen) // 'Foo' * getUniqueName('Foo', seen) // 'Foo2' * getUniqueName('Foo', seen) // 'Foo3' */ function getUniqueName(originalName, data) { let used = data[originalName] || 0; if (used) { data[originalName] = ++used; originalName += used; } data[originalName] = 1; return originalName; } //#endregion //#region ../../internals/utils/src/string.ts /** * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`. * Returns the string unchanged when no balanced quote pair is found. * * @example * trimQuotes('"hello"') // 'hello' * trimQuotes('hello') // 'hello' */ function trimQuotes(text) { if (text.length >= 2) { const first = text[0]; const last = text[text.length - 1]; if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1); } return text; } //#endregion //#region ../../internals/utils/src/object.ts /** * Serializes a primitive value to a JSON string literal, stripping any surrounding quote characters first. * * @example * stringify('hello') // '"hello"' * stringify('"hello"') // '"hello"' */ function stringify(value) { if (value === void 0 || value === null) return "\"\""; return JSON.stringify(trimQuotes(value.toString())); } //#endregion //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js var Node = class { value; next; constructor(value) { this.value = value; } }; var Queue = class { #head; #tail; #size; constructor() { this.clear(); } enqueue(value) { const node = new Node(value); if (this.#head) { this.#tail.next = node; this.#tail = node; } else { this.#head = node; this.#tail = node; } this.#size++; } dequeue() { const current = this.#head; if (!current) return; this.#head = this.#head.next; this.#size--; if (!this.#head) this.#tail = void 0; return current.value; } peek() { if (!this.#head) return; return this.#head.value; } clear() { this.#head = void 0; this.#tail = void 0; this.#size = 0; } get size() { return this.#size; } *[Symbol.iterator]() { let current = this.#head; while (current) { yield current.value; current = current.next; } } *drain() { while (this.#head) yield this.dequeue(); } }; //#endregion //#region ../../node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js function pLimit(concurrency) { let rejectOnClear = false; if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency); validateConcurrency(concurrency); if (typeof rejectOnClear !== "boolean") throw new TypeError("Expected `rejectOnClear` to be a boolean"); const queue = new Queue(); let activeCount = 0; const resumeNext = () => { if (activeCount < concurrency && queue.size > 0) { activeCount++; queue.dequeue().run(); } }; const next = () => { activeCount--; resumeNext(); }; const run = async (function_, resolve, arguments_) => { const result = (async () => function_(...arguments_))(); resolve(result); try { await result; } catch {} next(); }; const enqueue = (function_, resolve, reject, arguments_) => { const queueItem = { reject }; new Promise((internalResolve) => { queueItem.run = internalResolve; queue.enqueue(queueItem); }).then(run.bind(void 0, function_, resolve, arguments_)); if (activeCount < concurrency) resumeNext(); }; const generator = (function_, ...arguments_) => new Promise((resolve, reject) => { enqueue(function_, resolve, reject, arguments_); }); Object.defineProperties(generator, { activeCount: { get: () => activeCount }, pendingCount: { get: () => queue.size }, clearQueue: { value() { if (!rejectOnClear) { queue.clear(); return; } const abortError = AbortSignal.abort().reason; while (queue.size > 0) queue.dequeue().reject(abortError); } }, concurrency: { get: () => concurrency, set(newConcurrency) { validateConcurrency(newConcurrency); concurrency = newConcurrency; queueMicrotask(() => { while (activeCount < concurrency && queue.size > 0) resumeNext(); }); } }, map: { async value(iterable, function_) { const promises = Array.from(iterable, (value, index) => this(function_, value, index)); return Promise.all(promises); } } }); return generator; } function validateConcurrency(concurrency) { if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up"); } //#endregion //#region src/utils/getSchemaFactory.ts /** * Creates a factory function that generates a versioned OpenAPI schema result. * * The returned function accepts an optional schema object and produces a {@link SchemaResult} containing the dereferenced schema and the OpenAPI version ('3.0' or '3.1'). * * @returns A function that takes an optional schema and returns a versioned schema result. */ function getSchemaFactory(oas) { return (schema) => { const version = isOpenApiV3_1Document(oas.api) ? "3.1" : "3.0"; return { schemaObject: oas.dereferenceWithRef(schema), version }; }; } //#endregion //#region src/utils.tsx function isBuildOperationsV1Options(options) { return (options.version ?? "1") === "1"; } async function buildOperations(operationsOrNodes, options) { const { config, fabric, plugin } = options; if (!options.Component) return; const fabricChild = createReactFabric(); if (isBuildOperationsV1Options(options)) { const { generator, Component } = options; const { pluginManager, oas, mode } = generator.context; await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { meta: { pluginManager, plugin, mode, oas }, children: /* @__PURE__ */ jsx(Component, { config, operations: operationsOrNodes, generator, plugin }) })); } else { const { Component } = options; await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { meta: { plugin }, children: /* @__PURE__ */ jsx(Component, { config, nodes: operationsOrNodes, plugin }) })); } await fabric.context.fileManager.upsert(...fabricChild.files); fabricChild.unmount(); } function isBuildOperationV1Options(options) { return (options.version ?? "1") === "1"; } async function buildOperation(operationOrNode, options) { const { config, fabric, plugin } = options; if (!options.Component) return; const fabricChild = createReactFabric(); if (isBuildOperationV1Options(options)) { const { generator, Component } = options; const { pluginManager, oas, mode } = generator.context; await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { meta: { pluginManager, plugin, mode, oas }, children: /* @__PURE__ */ jsx(Component, { config, operation: operationOrNode, plugin, generator }) })); } else { const { Component } = options; await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { meta: { plugin }, children: /* @__PURE__ */ jsx(Component, { config, node: operationOrNode, plugin }) })); } await fabric.context.fileManager.upsert(...fabricChild.files); fabricChild.unmount(); } function isBuildSchemaV1Options(options) { return (options.version ?? "1") === "1"; } async function buildSchema(schema, options) { const { config, fabric, plugin } = options; if (!options.Component) return; const fabricChild = createReactFabric(); if (isBuildSchemaV1Options(options)) { const { generator, Component } = options; const { pluginManager, oas, mode } = generator.context; await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { meta: { pluginManager, plugin, mode, oas }, children: /* @__PURE__ */ jsx(Component, { config, schema, plugin, generator }) })); } else { const { Component } = options; await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { meta: { plugin }, children: /* @__PURE__ */ jsx(Component, { config, node: schema, plugin }) })); } await fabric.context.fileManager.upsert(...fabricChild.files); fabricChild.unmount(); } //#endregion //#region src/SchemaGenerator.ts /** Max concurrent generator tasks (across generators). */ const GENERATOR_CONCURRENCY = 3; /** Max concurrent schema parsing tasks (per generator). */ const SCHEMA_CONCURRENCY = 30; var SchemaGenerator = class SchemaGenerator { #options; #context; constructor(options, context) { this.#options = options; this.#context = context; } get options() { return this.#options; } set options(options) { this.#options = { ...this.#options, ...options }; } get context() { return this.#context; } refs = {}; #schemaNameMapping = /* @__PURE__ */ new Map(); #nameMappingInitialized = false; #parseCache = /* @__PURE__ */ new Map(); /** * Ensure the name mapping is initialized (lazy initialization) */ #ensureNameMapping() { if (this.#nameMappingInitialized) return; const { oas, contentType, include } = this.context; const { nameMapping } = oas.getSchemas({ contentType, includes: include }); this.#schemaNameMapping = nameMapping; this.#nameMappingInitialized = true; } /** * 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.name); let shouldCache = props.schema && typeof props.schema === "object" && !options.transformers?.schema; let cacheKey = ""; if (shouldCache) try { cacheKey = JSON.stringify({ schema: props.schema, name: props.name, parentName: props.parentName, rootName: props.rootName }); const cached = this.#parseCache.get(cacheKey); if (cached) return cached; } catch { shouldCache = false; } const defaultSchemas = this.#parseSchemaObject(props); const result = uniqueWith(options.transformers?.schema?.(props, defaultSchemas) || defaultSchemas || [], isDeepEqual); if (shouldCache && cacheKey) this.#parseCache.set(cacheKey, result); return result; } 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 (schema.args?.patternProperties) Object.values(schema.args.patternProperties).forEach((entrySchemas) => { entrySchemas.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 find(tree, keyword) { let foundItem; 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; } static combineObjects(tree) { if (!tree) return []; return tree.map((schema) => { if (!isKeyword(schema, schemaKeywords.and)) return schema; let mergedProperties = null; let mergedAdditionalProps = []; const newArgs = []; for (const subSchema of schema.args) if (isKeyword(subSchema, schemaKeywords.object)) { const { properties = {}, additionalProperties = [] } = subSchema.args ?? {}; if (!mergedProperties) mergedProperties = {}; for (const [key, value] of Object.entries(properties)) mergedProperties[key] = value; if (additionalProperties.length > 0) mergedAdditionalProps = additionalProperties; } else newArgs.push(subSchema); if (mergedProperties) newArgs.push({ keyword: schemaKeywords.object, args: { properties: mergedProperties, additionalProperties: mergedAdditionalProps } }); return { keyword: schemaKeywords.and, args: newArgs }; }); } #getOptions(name) { const { override = [] } = this.context; return { ...this.options, ...override.find(({ pattern, type }) => { if (name && type === "schemaName") return !!name.match(pattern); return false; })?.options || {} }; } #getUnknownType(name) { const options = this.#getOptions(name); if (options.unknownType === "any") return schemaKeywords.any; if (options.unknownType === "void") return schemaKeywords.void; return schemaKeywords.unknown; } #getEmptyType(name) { const options = this.#getOptions(name); if (options.emptySchemaType === "any") return schemaKeywords.any; if (options.emptySchemaType === "void") return schemaKeywords.void; return schemaKeywords.unknown; } /** * Recursively creates a type literal with the given props. */ #parseProperties(name, schemaObject, rootName) { const properties = schemaObject?.properties || {}; const additionalProperties = schemaObject?.additionalProperties; const required = schemaObject?.required; const patternProperties = schemaObject && "patternProperties" in schemaObject ? schemaObject.patternProperties : void 0; const propertiesSchemas = Object.keys(properties).map((propertyName) => { const validationFunctions = []; const propertySchema = properties[propertyName]; const isRequired = Array.isArray(required) ? required?.includes(propertyName) : !!required; const nullable = isNullable(propertySchema); validationFunctions.push(...this.parse({ schema: propertySchema, name: propertyName, parentName: name, rootName: rootName || 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.#getUnknownType(name) }] : this.parse({ schema: additionalProperties, name: null, parentName: name, rootName: rootName || name }); let patternPropertiesSchemas = {}; if (patternProperties && typeof patternProperties === "object") patternPropertiesSchemas = Object.entries(patternProperties).reduce((acc, [pattern, patternSchema]) => { const schemas = patternSchema === true || !Object.keys(patternSchema).length ? [{ keyword: this.#getUnknownType(name) }] : this.parse({ schema: patternSchema, name: null, parentName: name, rootName: rootName || name }); return { ...acc, [pattern]: schemas }; }, {}); const args = { properties: propertiesSchemas, additionalProperties: additionalPropertiesSchemas }; if (Object.keys(patternPropertiesSchemas).length > 0) args["patternProperties"] = patternPropertiesSchemas; return [{ keyword: schemaKeywords.object, args }]; } /** * Create a type alias for the schema referenced by the given ReferenceObject */ #getRefAlias(schemaObject, name) { const { $ref } = schemaObject; const ref = this.refs[$ref]; if (ref) { const dereferencedSchema = this.context.oas.dereferenceWithRef(schemaObject); if (dereferencedSchema && isDiscriminator(dereferencedSchema)) { const [key] = Object.entries(dereferencedSchema.discriminator.mapping || {}).find(([_key, value]) => value.replace(/.+\//, "") === name) || []; if (key) return [{ keyword: schemaKeywords.and, args: [{ keyword: schemaKeywords.ref, args: { name: ref.propertyName, $ref, path: ref.path, isImportable: !!this.context.oas.get($ref) } }, { keyword: schemaKeywords.object, args: { properties: { [dereferencedSchema.discriminator.propertyName]: [{ keyword: schemaKeywords.const, args: { name: key, format: "string", value: key } }] } } }] }]; } return [{ keyword: schemaKeywords.ref, args: { name: ref.propertyName, $ref, path: ref.path, isImportable: !!this.context.oas.get($ref) } }]; } if ($ref.startsWith("#") && !$ref.startsWith("#/components/")) try { const inlineSchema = this.context.oas.get($ref); if (inlineSchema && !isReference(inlineSchema)) return this.parse({ schema: inlineSchema, name, parentName: null, rootName: null }); } catch {} this.#ensureNameMapping(); const originalName = $ref.replace(/.+\//, ""); const resolvedName = this.#schemaNameMapping.get($ref) || originalName; const propertyName = this.context.pluginManager.resolveName({ name: resolvedName, pluginKey: this.context.plugin.key, type: "function" }); const fileName = this.context.pluginManager.resolveName({ name: resolvedName, pluginKey: this.context.plugin.key, type: "file" }); const file = this.context.pluginManager.getFile({ name: fileName, pluginKey: this.context.plugin.key, extname: ".ts" }); this.refs[$ref] = { propertyName, originalName: resolvedName, path: file.path }; return this.#getRefAlias(schemaObject, name); } #getParsedSchemaObject(schema) { return getSchemaFactory(this.context.oas)(schema); } #addDiscriminatorToSchema({ schema, schemaObject, discriminator }) { if (!isKeyword(schema, schemaKeywords.union)) return schema; if (discriminator.propertyName.startsWith("x-")) return schema; const objectPropertySchema = SchemaGenerator.find(this.parse({ schema: schemaObject, name: null, parentName: null, rootName: null }), schemaKeywords.object); return { ...schema, args: Object.entries(discriminator.mapping || {}).map(([key, value]) => { let arg; if (value.startsWith(KUBB_INLINE_REF_PREFIX)) { const index = Number.parseInt(value.replace(KUBB_INLINE_REF_PREFIX, ""), 10); if (!Number.isNaN(index) && index >= 0 && index < schema.args.length) arg = schema.args[index]; } else arg = schema.args.find((item) => isKeyword(item, schemaKeywords.ref) && item.args.$ref === value); if (!arg) return; return { keyword: schemaKeywords.and, args: [arg, { keyword: schemaKeywords.object, args: { properties: { ...objectPropertySchema?.args?.properties || {}, [discriminator.propertyName]: [{ keyword: schemaKeywords.const, args: { name: key, format: "string", value: key } }, ...objectPropertySchema?.args?.properties[discriminator.propertyName] || []].filter((item) => !isKeyword(item, schemaKeywords.enum)) } } }] }; }).filter(Boolean) }; } /** * Checks if an allOf item reference would create a circular reference. * This happens when a child schema extends a discriminator parent via allOf, * and the parent has a oneOf/anyOf that references or maps to the child. * * Without oneOf/anyOf, the discriminator is just for documentation/validation * purposes and doesn't create a TypeScript union type that would be circular. */ #wouldCreateCircularReference(item, childSchemaName) { if (!isReference(item) || !childSchemaName) return false; const dereferencedSchema = this.context.oas.dereferenceWithRef(item); if (dereferencedSchema && isDiscriminator(dereferencedSchema)) { const parentOneOf = dereferencedSchema.oneOf || dereferencedSchema.anyOf; if (!parentOneOf) return false; const childRef = `#/components/schemas/${childSchemaName}`; if (parentOneOf.some((oneOfItem) => { return isReference(oneOfItem) && oneOfItem.$ref === childRef; })) return true; const mapping = dereferencedSchema.discriminator.mapping || {}; if (Object.values(mapping).some((value) => value === childRef)) return true; } return false; } /** * This is the very core of the OpenAPI to TS conversion - it takes a * schema and returns the appropriate type. */ #parseSchemaObject({ schema: _schemaObject, name, parentName, rootName }) { const normalizedSchema = this.context.oas.flattenSchema(_schemaObject); const { schemaObject, version } = this.#getParsedSchemaObject(normalizedSchema); const options = this.#getOptions(name); const emptyType = this.#getEmptyType(name); if (!schemaObject) return [{ keyword: emptyType }]; const baseItems = [{ keyword: schemaKeywords.schema, args: { type: schemaObject.type, format: schemaObject.format } }]; const min = schemaObject.minimum ?? schemaObject.minLength ?? schemaObject.minItems ?? void 0; const max = schemaObject.maximum ?? schemaObject.maxLength ?? schemaObject.maxItems ?? void 0; const exclusiveMinimum = schemaObject.exclusiveMinimum; const exclusiveMaximum = schemaObject.exclusiveMaximum; const nullable = isNullable(schemaObject); const defaultNullAndNullable = schemaObject.default === null && nullable; if (schemaObject.default !== void 0 && !defaultNullAndNullable && !Array.isArray(schemaObject.default)) if (typeof schemaObject.default === "string") baseItems.push({ keyword: schemaKeywords.default, args: stringify(schemaObject.default) }); else if (typeof schemaObject.default === "boolean") baseItems.push({ keyword: schemaKeywords.default, args: schemaObject.default ?? false }); else baseItems.push({ keyword: schemaKeywords.default, args: schemaObject.default }); if (schemaObject.deprecated) baseItems.push({ keyword: schemaKeywords.deprecated }); if (schemaObject.description) baseItems.push({ keyword: schemaKeywords.describe, args: schemaObject.description }); if (max !== void 0) if (exclusiveMaximum) baseItems.unshift({ keyword: schemaKeywords.exclusiveMaximum, args: max }); else baseItems.unshift({ keyword: schemaKeywords.max, args: max }); if (min !== void 0) if (exclusiveMinimum) baseItems.unshift({ keyword: schemaKeywords.exclusiveMinimum, args: min }); else baseItems.unshift({ keyword: schemaKeywords.min, args: min }); if (typeof exclusiveMaximum === "number") baseItems.unshift({ keyword: schemaKeywords.exclusiveMaximum, args: exclusiveMaximum }); if (typeof exclusiveMinimum === "number") baseItems.unshift({ keyword: schemaKeywords.exclusiveMinimum, args: exclusiveMinimum }); if (nullable) baseItems.push({ keyword: schemaKeywords.nullable }); if (schemaObject.type && Array.isArray(schemaObject.type)) { const items = schemaObject.type.filter((value) => value !== "null"); if (items.length > 1) return [...[{ keyword: schemaKeywords.union, args: items.map((item) => this.parse({ schema: { ...schemaObject, type: item }, name, parentName, rootName })[0]).filter((x) => Boolean(x)).map((item) => isKeyword(item, schemaKeywords.object) ? { ...item, args: { ...item.args, strict: true } } : item) }], ...baseItems].filter(Boolean); } if (schemaObject.readOnly) baseItems.push({ keyword: schemaKeywords.readOnly }); if (schemaObject.writeOnly) baseItems.push({ keyword: schemaKeywords.writeOnly }); if (isReference(schemaObject)) return [ ...this.#getRefAlias(schemaObject, name), ...baseItems.filter((item) => item.keyword === schemaKeywords.default), schemaObject.description && { keyword: schemaKeywords.describe, args: schemaObject.description }, schemaObject.pattern && schemaObject.type === "string" && { keyword: schemaKeywords.matches, args: schemaObject.pattern }, nullable && { keyword: schemaKeywords.nullable }, schemaObject.readOnly && { keyword: schemaKeywords.readOnly }, schemaObject.writeOnly && { keyword: schemaKeywords.writeOnly }, { keyword: schemaKeywords.schema, args: { type: schemaObject.type, format: schemaObject.format } } ].filter((x) => Boolean(x)); if (schemaObject.oneOf || schemaObject.anyOf) { const schemaWithoutOneOf = { ...schemaObject, oneOf: void 0, anyOf: void 0 }; const discriminator = this.context.oas.getDiscriminator(schemaObject); const union = { keyword: schemaKeywords.union, args: (schemaObject.oneOf || schemaObject.anyOf).map((item) => { return item && this.parse({ schema: item, name, parentName, rootName })[0]; }).filter((x) => Boolean(x)) }; if (discriminator) { if (this.context && this.context.oas.options.discriminator !== "inherit") return [this.#addDiscriminatorToSchema({ schemaObject: schemaWithoutOneOf, schema: union, discriminator }), ...baseItems]; } if (schemaWithoutOneOf.properties) { const propertySchemas = this.parse({ schema: schemaWithoutOneOf, name, parentName, rootName }); union.args = [...union.args.map((arg) => { return { keyword: schemaKeywords.and, args: [arg, ...propertySchemas] }; })]; return [union, ...baseItems]; } return [union, ...baseItems]; } if (schemaObject.allOf) { const schemaWithoutAllOf = { ...schemaObject, allOf: void 0 }; const and = { keyword: schemaKeywords.and, args: schemaObject.allOf.flatMap((item) => { if (this.#wouldCreateCircularReference(item, name)) return []; return item ? this.parse({ schema: item, name, parentName, rootName }) : []; }).filter(Boolean) }; if (schemaWithoutAllOf.required?.length) { const allOfItems = schemaObject.allOf; const resolvedSchemas = []; for (const item of allOfItems) { const resolved = isReference(item) ? this.context.oas.get(item.$ref) : item; if (resolved) resolvedSchemas.push(resolved); } const existingKeys = schemaWithoutAllOf.properties ? new Set(Object.keys(schemaWithoutAllOf.properties)) : null; const parsedItems = []; for (const key of schemaWithoutAllOf.required) { if (existingKeys?.has(key)) continue; for (const schema of resolvedSchemas) if (schema.properties?.[key]) { parsedItems.push({ properties: { [key]: schema.properties[key] }, required: [key] }); break; } } for (const item of parsedItems) { const parsed = this.parse({ schema: item, name, parentName, rootName }); if (Array.isArray(parsed)) and.args = and.args ? and.args.concat(parsed) : parsed; } } if (schemaWithoutAllOf.properties) and.args = [...and.args || [], ...this.parse({ schema: schemaWithoutAllOf, name, parentName, rootName })]; return SchemaGenerator.combineObjects([and, ...baseItems]); } if (schemaObject.enum) { if (schemaObject.type === "array") { const normalizedItems = { ...typeof schemaObject.items === "object" && !Array.isArray(schemaObject.items) ? schemaObject.items : {}, enum: schemaObject.enum }; const { enum: _, ...schemaWithoutEnum } = schemaObject; const normalizedSchema = { ...schemaWithoutEnum, items: normalizedItems }; return this.parse({ schema: normalizedSchema, name, parentName, rootName }); } const useCollisionDetection = this.context.oas.options.collisionDetection ?? false; const enumNameParts = useCollisionDetection && rootName && rootName !== parentName ? [ rootName, parentName, name, options.enumSuffix ] : [ parentName, name, options.enumSuffix ]; const enumName = useCollisionDetection ? pascalCase(enumNameParts.join(" ")) : getUniqueName(pascalCase(enumNameParts.join(" ")), this.options.usedEnumNames || {}); const typeName = this.context.pluginManager.resolveName({ name: enumName, pluginKey: this.context.plugin.key, type: "type" }); if (schemaObject.enum.includes(null)) baseItems.push({ keyword: schemaKeywords.nullable }); const filteredValues = schemaObject.enum.filter((value) => value !== null); const extensionEnums = ["x-enumNames", "x-enum-varnames"].filter((extensionKey) => extensionKey in schemaObject).map((extensionKey) => { return [{ keyword: schemaKeywords.enum, args: { name, typeName, asConst: false, items: [...new Set(schemaObject[extensionKey])].map((name, index) => ({ name: stringify(name), value: schemaObject.enum?.[index], format: isNumber(schemaObject.enum?.[index]) ? "number" : "string" })) } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max && item.keyword !== schemaKeywords.matches)]; }); if (schemaObject.type === "number" || schemaObject.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, value }) => ({ name, 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 (schemaObject.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, value }) => ({ name, 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: stringify(value), value, format: isNumber(value) ? "number" : "string" })) } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max && item.keyword !== schemaKeywords.matches)]; } if ("prefixItems" in schemaObject) { const prefixItems = schemaObject.prefixItems; const items = "items" in schemaObject ? schemaObject.items : []; const min = schemaObject.minimum ?? schemaObject.minLength ?? schemaObject.minItems ?? void 0; const max = schemaObject.maximum ?? schemaObject.maxLength ?? schemaObject.maxItems ?? void 0; return [{ keyword: schemaKeywords.tuple, args: { min, max, items: prefixItems.map((item) => { return this.parse({ schema: item, name, parentName, rootName })[0]; }).filter((x) => Boolean(x)), rest: this.parse({ schema: items, name, parentName, rootName })[0] } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max)]; } if (version === "3.1" && "const" in schemaObject) { if (schemaObject["const"] === null) return [{ keyword: schemaKeywords.null }]; if (schemaObject["const"] === void 0) return [{ keyword: schemaKeywords.undefined }]; let format = typeof schemaObject["const"]; if (format !== "number" && format !== "boolean") format = "string"; return [{ keyword: schemaKeywords.const, args: { name: schemaObject["const"], format, value: schemaObject["const"] } }, ...baseItems]; } /** * > Structural validation alone may be insufficient to allow an application to correctly utilize certain values. The "format" * > annotation keyword is defined to allow schema authors to convey semantic information for a fixed subset of values which are * > accurately described by authoritative resources, be they RFCs or other external specifications. * * In other words: format is more specific than type alone, hence it should override the type value, if possible. * * see also https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7 */ if (schemaObject.type === "string" && schemaObject.contentMediaType === "application/octet-stream") { baseItems.unshift({ keyword: schemaKeywords.blob }); return baseItems; } if (schemaObject.format) { if (schemaObject.type === "integer" && schemaObject.format === "int64") { baseItems.unshift({ keyword: options.integerType === "bigint" ? schemaKeywords.bigint : schemaKeywords.integer }); return baseItems; } if (schemaObject.type === "integer" && schemaObject.format === "int32") { baseItems.unshift({ keyword: schemaKeywords.integer }); return baseItems; } if (schemaObject.type === "number" && (schemaObject.format === "float" || schemaObject.format === "double")) { baseItems.unshift({ keyword: schemaKeywords.number }); return baseItems; } switch (schemaObject.format) { case "binary": baseItems.unshift({ 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; default: break; } } if (schemaObject.pattern && schemaObject.type === "string") { baseItems.unshift({ keyword: schemaKeywords.matches, args: schemaObject.pattern }); return baseItems; } if ("items" in schemaObject || schemaObject.type === "array") { const min = schemaObject.minimum ?? schemaObject.minLength ?? schemaObject.minItems ?? void 0; const max = schemaObject.maximum ?? schemaObject.maxLength ?? schemaObject.maxItems ?? void 0; const items = this.parse({ schema: "items" in schemaObject ? schemaObject.items : [], name, parentName, rootName }); const unique = !!schemaObject.uniqueItems; return [{ keyword: schemaKeywords.array, args: { items, min, max, unique } }, ...baseItems.filter((item) => item.keyword !== schemaKeywords.min && item.keyword !== schemaKeywords.max)]; } if (schemaObject.properties || schemaObject.additionalProperties || "patternProperties" in schemaObject) { if (isDiscriminator(schemaObject)) { const schemaObjectOverridden = Object.keys(schemaObject.properties || {}).reduce((acc, propertyName) => { if (acc.properties?.[propertyName] && propertyName === schemaObject.discriminator.propertyName) { const existingProperty = acc.properties[propertyName]; return { ...acc, properties: { ...acc.properties, [propertyName]: { ...existingProperty, enum: schemaObject.discriminator.mapping ? Object.keys(schemaObject.discriminator.mapping) : void 0 } } }; } return acc; }, schemaObject); return [...this.#parseProperties(name, schemaObjectOverridden, rootName), ...baseItems]; } return [...this.#parseProperties(name, schemaObject, rootName), ...baseItems]; } if (schemaObject.type) { const type = Array.isArray(schemaObject.type) ? schemaObject.type.filter((item) => item !== "null")[0] : schemaObject.type; if (![ "boolean", "object", "number", "string", "integer", "null" ].includes(type)) this.context.events?.emit("warn", `Schema type '${schemaObject.type}' is not valid for schema ${parentName}.${name}`); return [{ keyword: type }, ...baseItems]; } let inferredType; if (schemaObject.minLength !== void 0 || schemaObject.maxLength !== void 0 || schemaObject.pattern !== void 0) inferredType = "string"; else if (schemaObject.minimum !== void 0 || schemaObject.maximum !== void 0) inferredType = "number"; if (inferredType) return [{ keyword: inferredType }, ...baseItems]; return [{ keyword: emptyType }, ...baseItems]; } async build(...generators) { const { oas, contentType, include } = this.context; if (!this.#nameMappingInitialized) { const { schemas, nameMapping } = oas.getSchemas({ contentType, includes: include }); this.#schemaNameMapping = nameMapping; this.#nameMappingInitialized = true; const schemaEntries = Object.entries(schemas); this.context.events?.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [ `Building ${schemaEntries.length} schemas`, ` • Content Type: ${contentType || "application/json"}`, ` • Generators: ${generators.length}` ] }); return this.#doBuild(schemas, generators); } const { schemas } = oas.getSchemas({ contentType, includes: include }); return this.#doBuild(schemas, generators); } async #doBuild(schemas, generators) { const schemaEntries = Object.entries(schemas); const generatorLimit = pLimit(GENERATOR_CONCURRENCY); const schemaLimit = pLimit(SCHEMA_CONCURRENCY); const writeTasks = generators.map((generator) => generatorLimit(async () => { if (generator.version === "2") return []; const v1Generator = generator; const schemaTasks = schemaEntries.map(([name, schemaObject]) => schemaLimit(async () => { const options = this.#getOptions(name); const tree = this.parse({ schema: schemaObject, name, parentName: null, rootName: name }); if (v1Generator.type === "react") { await buildSchema({ name, value: schemaObject, tree }, { config: this.context.pluginManager.config, fabric: this.context.fabric, Component: v1Generator.Schema, generator: this, plugin: { ...this.context.plugin, options: { ...this.options, ...options } } }); return []; } return await v1Generator.schema?.({ config: this.context.pluginManager.config, generator: this, schema: { name, value: schemaObject, tree }, plugin: { ...this.context.plugin, options: { ...this.options, ...options } } }) ?? []; })); return (await Promise.all(schemaTasks)).flat(); })); return (await Promise.all(writeTasks)).flat(); } }; //#endregion //#region src/utils/requestBody.ts /** * Sentinel value injected into `schema.required` to signal that the request body is required. * Downstream generators check for this marker to emit the correct required constraint. */ const KUBB_REQUIRED_REQUEST_BODY_MARKER = "__kubb_required_request_body__"; function getRequestBody(operationSchema) { const requestBody = operationSchema?.operation?.schema?.requestBody; if (!requestBody || "$ref" in requestBody) return; return requestBody; } function isRequestBodyRequired(operationSchema) { const requestBody = getRequestBody(operationSchema); return !!requestBody && requestBody.required === true; } function withRequiredRequestBodySchema(operationSchema) { if (!operationSchema || !isRequestBodyRequired(operationSchema)) return operationSchema; if (Array.isArray(operationSchema.schema.required) && operationSchema.schema.required.length > 0) return operationSchema; return { ...operationSchema, schema: { ...operationSchema.schema, required: [KUBB_REQUIRED_REQUEST_BODY_MARKER] } }; } //#endregion export { buildOperations as a, pLimit as c, buildOperation as i, withRequiredRequestBodySchema as n, buildSchema as o, SchemaGenerator as r, getSchemaFactory as s, isRequestBodyRequired as t }; //# sourceMappingURL=requestBody-pRavthCw.js.map