@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
JavaScript
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