zod-openapi
Version:
Convert Zod Schemas to OpenAPI v3.x documentation
931 lines (914 loc) • 35.6 kB
JavaScript
import { globalRegistry } from "zod/v4/core";
import { globalRegistry as globalRegistry$1, object, registry, toJSONSchema as toJSONSchema$1 } from "zod/v4";
//#region src/zod.ts
const isAnyZodType = (schema) => typeof schema === "object" && schema !== null && "_zod" in schema;
//#endregion
//#region src/create/examples.ts
const createExamples = (examples, registry$1, path) => {
if (!examples) return;
const examplesObject = {};
for (const [name, example] of Object.entries(examples)) examplesObject[name] = registry$1.addExample(example, [...path, name]);
return examplesObject;
};
//#endregion
//#region src/create/content.ts
const createMediaTypeObject = (mediaType, ctx, path) => {
const { schema, examples, ...rest } = mediaType;
const mediaTypeObject = rest;
if (isAnyZodType(schema)) mediaTypeObject.schema = ctx.registry.addSchema(schema, [...path, "schema"], {
io: ctx.io,
source: { type: "mediaType" }
});
else mediaTypeObject.schema = schema;
if (examples) mediaTypeObject.examples = createExamples(examples, ctx.registry, [...path, "examples"]);
return mediaTypeObject;
};
const createContent = (content, ctx, path) => {
const contentObject = {};
for (const [mediaType, mediaTypeObject] of Object.entries(content)) if (mediaTypeObject) contentObject[mediaType] = createMediaTypeObject(mediaTypeObject, ctx, [...path, mediaType]);
return contentObject;
};
//#endregion
//#region src/create/object.ts
const unwrapZodObject = (zodType, io, path) => {
const def = zodType._zod.def;
switch (def.type) {
case "object": return zodType;
case "lazy": return unwrapZodObject(def.getter(), io, path);
case "pipe":
if (io === "input") return unwrapZodObject(def.in, io, path);
return unwrapZodObject(def.out, io, path);
}
throw new Error(`Failed to unwrap ZodObject from type: ${zodType._zod.def.type} at ${path.join(" > ")}`);
};
const isRequired = (zodType, io) => {
if (io === "input") return zodType._zod.optin === void 0;
return zodType._zod.optout === void 0;
};
//#endregion
//#region src/create/headers.ts
const createHeaders = (headers, registry$1, path) => {
if (!headers) return;
if (isAnyZodType(headers)) {
const zodObject = unwrapZodObject(headers, "output", path);
const headersObject = {};
for (const [key, zodSchema] of Object.entries(zodObject._zod.def.shape)) headersObject[key] = registry$1.addHeader(zodSchema, [...path, key]);
return headersObject;
}
return headers;
};
//#endregion
//#region src/create/links.ts
const createLinks = (links, registry$1, path) => {
if (!links) return;
const linksObject = {};
for (const [name, link] of Object.entries(links)) linksObject[name] = registry$1.addLink(link, [...path, name]);
return linksObject;
};
//#endregion
//#region src/create/parameters.ts
const createManualParameters = (parameters, registry$1, path) => {
if (!parameters) return;
const parameterObjects = [];
for (const parameter of parameters) {
if (isAnyZodType(parameter)) {
const paramObject = registry$1.addParameter(parameter, [...path, "parameters"]);
parameterObjects.push(paramObject);
continue;
}
parameterObjects.push(parameter);
}
return parameterObjects;
};
const createParameters = (requestParams, registry$1, path) => {
if (!requestParams) return;
const parameterObjects = [];
for (const [location, schema] of Object.entries(requestParams ?? {})) {
const zodObject = unwrapZodObject(schema, "input", path);
for (const [name, zodSchema] of Object.entries(zodObject._zod.def.shape)) {
const paramObject = registry$1.addParameter(zodSchema, [
...path,
location,
name
], { location: {
in: location,
name
} });
parameterObjects.push(paramObject);
}
}
return parameterObjects;
};
//#endregion
//#region src/create/specificationExtension.ts
const isISpecificationExtension = (key) => key.startsWith("x-");
//#endregion
//#region src/create/callbacks.ts
const createCallbacks = (callbacks, registry$1, path) => {
if (!callbacks) return;
const callbacksObject = {};
for (const [name, value] of Object.entries(callbacks)) {
if (isISpecificationExtension(name)) {
callbacksObject[name] = value;
continue;
}
callbacksObject[name] = registry$1.addCallback(value, [...path, name]);
}
return callbacksObject;
};
//#endregion
//#region src/create/responses.ts
const createResponses = (responses, registry$1, path) => {
if (!responses) return;
const responsesObject = {};
for (const [statusCode, response] of Object.entries(responses)) {
if (!response) continue;
if (isISpecificationExtension(statusCode)) {
responsesObject[statusCode] = response;
continue;
}
if ("$ref" in response) {
responsesObject[statusCode] = response;
continue;
}
responsesObject[statusCode] = registry$1.addResponse(response, [...path, statusCode]);
}
return responsesObject;
};
//#endregion
//#region src/create/paths.ts
const createOperation = (operation, registry$1, path) => {
const { parameters, requestParams, requestBody, responses, callbacks, ...rest } = operation;
const operationObject = rest;
const maybeManualParameters = createManualParameters(parameters, registry$1, [...path, "parameters"]);
const maybeRequestParams = createParameters(requestParams, registry$1, [...path, "requestParams"]);
if (maybeRequestParams || maybeManualParameters) operationObject.parameters = [...maybeRequestParams ?? [], ...maybeManualParameters ?? []];
const maybeRequestBody = requestBody && registry$1.addRequestBody(requestBody, path);
if (maybeRequestBody) operationObject.requestBody = maybeRequestBody;
const maybeResponses = createResponses(responses, registry$1, [...path, "responses"]);
if (maybeResponses) operationObject.responses = maybeResponses;
const maybeCallbacks = createCallbacks(callbacks, registry$1, [...path, "callbacks"]);
if (maybeCallbacks) operationObject.callbacks = maybeCallbacks;
return operationObject;
};
const createPaths = (paths, registry$1, path) => {
if (!paths) return;
const pathsObject = {};
for (const [singlePath, pathItemObject] of Object.entries(paths)) {
if (isISpecificationExtension(singlePath)) {
pathsObject[singlePath] = pathItemObject;
continue;
}
pathsObject[singlePath] = registry$1.addPathItem(pathItemObject, [...path, singlePath]);
}
return pathsObject;
};
//#endregion
//#region src/openapi.ts
const openApiVersions = [
"3.0.0",
"3.0.1",
"3.0.2",
"3.0.3",
"3.1.0",
"3.1.1"
];
const satisfiesVersion = (test, against) => openApiVersions.indexOf(test) >= openApiVersions.indexOf(against);
//#endregion
//#region src/create/schema/override.ts
const override = (ctx) => {
const def = ctx.zodSchema._zod.def;
switch (def.type) {
case "bigint":
ctx.jsonSchema.type = "integer";
ctx.jsonSchema.format = "int64";
break;
case "union": {
if ("discriminator" in def && typeof def.discriminator === "string") {
ctx.jsonSchema.oneOf ??= ctx.jsonSchema.anyOf;
delete ctx.jsonSchema.anyOf;
ctx.jsonSchema.type = "object";
ctx.jsonSchema.discriminator = { propertyName: def.discriminator };
const mapping = {};
for (const [index, obj] of Object.entries(ctx.jsonSchema.oneOf)) {
const ref = obj.$ref;
if (!ref) {
delete ctx.jsonSchema.discriminator;
return;
}
const discriminatorValues = def.options[Number(index)]._zod.propValues?.[def.discriminator];
if (!discriminatorValues?.size) return;
for (const value of [...discriminatorValues ?? []]) {
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") return;
mapping[String(value)] = ref;
}
}
ctx.jsonSchema.discriminator.mapping = mapping;
}
const meta = globalRegistry$1.get(ctx.zodSchema);
if (typeof meta?.unionOneOf === "boolean") {
if (meta.unionOneOf) {
ctx.jsonSchema.oneOf = ctx.jsonSchema.anyOf;
delete ctx.jsonSchema.anyOf;
}
delete ctx.jsonSchema.unionOneOf;
}
break;
}
case "date":
ctx.jsonSchema.type = "string";
break;
case "literal":
if (def.values.includes(void 0)) break;
break;
case "undefined":
ctx.jsonSchema.not = {};
break;
}
};
const validate = (ctx, opts, previousContext) => {
if (previousContext.context && ctx.zodSchema._zod.parent !== previousContext.context.zodSchema) {
if (previousContext.context.zodSchema._zod.def.type === "pipe") throw new Error(`Zod transform found at ${previousContext.context.path.join(" > ")} are not supported in output schemas. Please use \`.overwrite()\` or wrap the schema in a \`.pipe()\` or assign it manual metadata with \`.meta()\``);
throw new Error(`Zod schema of type \`${previousContext.context.zodSchema._zod.def.type}\` at ${previousContext.context?.path.join(" > ")} cannot be represented in OpenAPI. Please assign it metadata with \`.meta()\``);
}
previousContext.context = void 0;
if (Object.keys(ctx.jsonSchema).length) return;
const def = ctx.zodSchema._zod.def;
const allowEmptySchema = opts.allowEmptySchema?.[def.type];
if (allowEmptySchema === true || allowEmptySchema?.[ctx.io]) return;
switch (def.type) {
case "optional":
validate({
...ctx,
zodSchema: def.innerType
}, opts, previousContext);
return;
case "any": return;
case "unknown": return;
case "pipe":
if (ctx.io === "output") {
if (!ctx.zodSchema._zod.parent) {
previousContext.context = ctx;
return;
}
}
return;
case "transform":
if (ctx.io === "output") return;
break;
case "literal":
if (def.values.includes(void 0)) throw new Error(`Zod literal at ${ctx.path.join(" > ")} cannot include \`undefined\` as a value. Please use \`z.undefined()\` or \`.optional()\` instead.`);
return;
case "custom": if (!ctx.zodSchema._zod.parent) {
previousContext.context = ctx;
return;
}
}
throw new Error(`Zod schema of type \`${def.type}\` at ${ctx.path.join(" > ")} cannot be represented in OpenAPI. Please assign it metadata with \`.meta()\``);
};
//#endregion
//#region src/create/schema/rename.ts
const renameComponents = (components, outputIds, ctx, refPath) => {
const componentsToRename = /* @__PURE__ */ new Map();
if (ctx.io === "input") return componentsToRename;
const componentDependencies = /* @__PURE__ */ new Map();
const stringifiedComponents = /* @__PURE__ */ new Map();
for (const [key, value] of Object.entries(components)) {
const stringified = JSON.stringify(value);
const regex = new RegExp(`"${refPath}([^"]+)"`, "g");
const matches = stringified.matchAll(regex);
const dependencies = /* @__PURE__ */ new Set();
for (const match of matches) {
const dep = match[1];
if (dep !== key) dependencies.add(dep);
}
stringifiedComponents.set(key, stringified);
componentDependencies.set(key, { dependencies });
}
for (const [key] of stringifiedComponents) {
if (!ctx.registry.components.schemas.ids.get(key)) continue;
if (isDependencyPure(componentDependencies, stringifiedComponents, ctx.registry, key)) continue;
const newName = outputIds.get(key) ?? `${key}${ctx.opts.outputIdSuffix ?? "Output"}`;
componentsToRename.set(key, newName);
components[newName] = components[key];
delete components[key];
}
return componentsToRename;
};
const isDependencyPure = (componentDependencies, stringifiedComponents, registry$1, key, visited = /* @__PURE__ */ new Set()) => {
if (visited.has(key)) return true;
const dependencies = componentDependencies.get(key);
if (dependencies.pure !== void 0) return dependencies.pure;
const stringified = stringifiedComponents.get(key);
const component = registry$1.components.schemas.ids.get(key);
if (component && stringified !== JSON.stringify(component)) {
dependencies.pure = false;
return false;
}
visited.add(key);
const result = [...dependencies.dependencies].every((dep) => isDependencyPure(componentDependencies, stringifiedComponents, registry$1, dep, new Set(visited)));
dependencies.pure = result;
return result;
};
//#endregion
//#region src/create/schema/schema.ts
const createSchema = (schema, ctx = {}) => {
ctx.registry ??= createRegistry({ schemas: ctx.schemaComponents });
ctx.opts ??= {};
ctx.io ??= "output";
const registrySchemas = Object.fromEntries(ctx.registry.components.schemas[ctx.io]);
const schemas = { zodOpenApiCreateSchema: { zodType: schema } };
Object.assign(schemas, registrySchemas);
const jsonSchemas = createSchemas(schemas, {
registry: ctx.registry,
io: ctx.io,
opts: {
...ctx.opts,
schemaRefPath: ctx.schemaRefPath
},
openapiVersion: ctx.openapiVersion
});
return {
schema: jsonSchemas.schemas.zodOpenApiCreateSchema,
components: jsonSchemas.components
};
};
const zodOpenApiMetadataFields = [
"param",
"header",
"unusedIO",
"override",
"outputId"
];
const deleteZodOpenApiMeta = (jsonSchema) => {
zodOpenApiMetadataFields.forEach((field) => {
delete jsonSchema[field];
});
};
const deleteInvalidJsonSchemaFields = (jsonSchema) => {
delete jsonSchema.$schema;
delete jsonSchema.id;
delete jsonSchema.$id;
};
const createSchemas = (schemas, ctx) => {
const refPath = ctx.opts.schemaRefPath ?? "#/components/schemas/";
const entries = {};
for (const [name, { zodType }] of Object.entries(schemas)) entries[name] = zodType;
const zodRegistry = registry();
zodRegistry.add(object(entries), { id: "zodOpenApiCreateSchema" });
for (const [id, { zodType }] of ctx.registry.components.schemas.manual) zodRegistry.add(zodType, { id });
const outputIds = /* @__PURE__ */ new Map();
const defsName = satisfiesVersion(ctx.openapiVersion ?? "3.1.0", "3.1.0") ? "$defs" : "definitions";
const previousContext = {};
const jsonSchema = toJSONSchema$1(zodRegistry, {
override(context) {
const meta = globalRegistry$1.get(context.zodSchema);
if (meta?.outputId && meta?.id) outputIds.set(meta.id, meta.outputId);
if (context.jsonSchema.$ref) return;
const enrichedContext = {
...context,
io: ctx.io
};
override(enrichedContext);
if (typeof ctx.opts.override === "function") ctx.opts.override(enrichedContext);
if (typeof meta?.override === "function") {
meta.override(enrichedContext);
delete context.jsonSchema.override;
}
if (typeof meta?.override === "object" && meta.override !== null) {
Object.assign(context.jsonSchema, meta.override);
delete context.jsonSchema.override;
}
deleteInvalidJsonSchemaFields(context.jsonSchema);
deleteZodOpenApiMeta(context.jsonSchema);
validate(enrichedContext, ctx.opts, previousContext);
},
io: ctx.io,
unrepresentable: "any",
reused: ctx.opts.reused,
cycles: ctx.opts.cycles,
target: satisfiesVersion(ctx.openapiVersion ?? "3.1.0", "3.1.0") ? void 0 : "openapi-3.0",
uri: (id) => id === "__shared" ? `#ZOD_OPENAPI/${id}` : `#ZOD_OPENAPI/__shared#/${defsName}/${id}`
});
const components = jsonSchema.schemas.__shared?.[defsName] ?? {};
jsonSchema.schemas.__shared ??= { [defsName]: components };
const dynamicComponents = /* @__PURE__ */ new Map();
for (const [key, value] of Object.entries(components)) {
deleteInvalidJsonSchemaFields(value);
if (/^schema\d+$/.test(key)) {
const newName = `__schema${ctx.registry.components.schemas.dynamicSchemaCount++}`;
dynamicComponents.set(key, `"${refPath}${newName}"`);
if (newName !== key) {
components[newName] = value;
delete components[key];
}
}
}
for (const [key] of ctx.registry.components.schemas.manual) {
const manualComponent = jsonSchema.schemas[key];
if (!manualComponent) continue;
deleteInvalidJsonSchemaFields(manualComponent);
}
const manualUsed = {};
const parsedJsonSchema = JSON.parse(JSON.stringify(jsonSchema).replace(/"#ZOD_OPENAPI\/__shared#\/(?:\$defs|definitions)\/([^"]+)"/g, (_, match) => {
const dynamic = dynamicComponents.get(match);
if (dynamic) return dynamic;
if (ctx.registry.components.schemas.manual.get(match)) manualUsed[match] = true;
return `"${refPath}${match}"`;
}));
const parsedComponents = parsedJsonSchema.schemas.__shared?.[defsName] ?? {};
parsedJsonSchema.schemas.__shared ??= { [defsName]: parsedComponents };
for (const [key] of ctx.registry.components.schemas.manual) {
const manualComponent = parsedJsonSchema.schemas[key];
if (!manualComponent) continue;
if (manualUsed[key]) {
if (parsedComponents[key]) throw new Error(`Component "${key}" is already registered as a component in the registry`);
parsedComponents[key] = manualComponent;
}
}
const componentsToRename = renameComponents(parsedComponents, outputIds, ctx, refPath);
if (!componentsToRename.size) {
const parsedSchemas = parsedJsonSchema.schemas.zodOpenApiCreateSchema?.properties;
delete parsedJsonSchema.schemas.zodOpenApiCreateSchema;
delete parsedJsonSchema.schemas.__shared;
return {
schemas: parsedSchemas,
components: parsedComponents,
manual: parsedJsonSchema.schemas
};
}
const renamedJsonSchema = JSON.parse(JSON.stringify(parsedJsonSchema).replace(new RegExp(`"${refPath}([^"]+)"`, "g"), (_, match) => {
const replacement = componentsToRename.get(match);
if (replacement) return `"${refPath}${replacement}"`;
return `"${refPath}${match}"`;
}));
const renamedSchemas = renamedJsonSchema.schemas.zodOpenApiCreateSchema?.properties;
const renamedComponents = renamedJsonSchema.schemas.__shared?.[defsName] ?? {};
delete renamedJsonSchema.schemas.zodOpenApiCreateSchema;
delete renamedJsonSchema.schemas.__shared;
return {
schemas: renamedSchemas,
components: renamedComponents,
manual: renamedJsonSchema.schemas
};
};
//#endregion
//#region src/create/components.ts
const createRegistry = (components) => {
const registry$1 = {
components: {
schemas: {
dynamicSchemaCount: 0,
input: /* @__PURE__ */ new Map(),
output: /* @__PURE__ */ new Map(),
ids: /* @__PURE__ */ new Map(),
manual: /* @__PURE__ */ new Map()
},
headers: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
requestBodies: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
responses: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
parameters: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
callbacks: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
pathItems: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
securitySchemes: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
links: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
},
examples: {
ids: /* @__PURE__ */ new Map(),
seen: /* @__PURE__ */ new WeakMap()
}
},
addSchema: (schema, path, opts) => {
const schemaObject = {};
registry$1.components.schemas[opts.io].set(path.join(" > "), {
schemaObject,
zodType: schema,
source: {
path,
...opts?.source
}
});
return schemaObject;
},
addParameter: (parameter, path, opts) => {
const seenParameter = registry$1.components.parameters.seen.get(parameter);
if (seenParameter) return seenParameter;
const meta = globalRegistry.get(parameter);
const name = opts?.location?.name ?? meta?.param?.name;
const inLocation = opts?.location?.in ?? meta?.param?.in;
if (opts?.location?.name && meta?.param?.name || opts?.location?.in && meta?.param?.in) throw new Error(`Parameter at ${path.join(" > ")} has both \`.meta({ param: { name, in } })\` and \`.meta({ param: { location: { in, name } } })\` information`);
if (!name || !inLocation) throw new Error(`Parameter at ${path.join(" > ")} is missing \`.meta({ param: { name, in } })\` information`);
const schemaObject = registry$1.addSchema(parameter, [
...path,
inLocation,
name,
"schema"
], {
io: "input",
source: {
type: "parameter",
location: {
in: inLocation,
name
}
}
});
const { id: metaId, examples, ...rest } = meta?.param ?? {};
const parameterObject = {
in: inLocation,
name,
schema: schemaObject,
...rest
};
const examplesObject = createExamples(examples, registry$1, [
...path,
inLocation,
name,
"examples"
]);
if (examplesObject) parameterObject.examples = examplesObject;
if (isRequired(parameter, "input")) parameterObject.required = true;
if (!parameterObject.description && meta?.description) parameterObject.description = meta.description;
const id = metaId ?? opts?.manualId;
if (id) {
if (registry$1.components.parameters.ids.has(id)) throw new Error(`Schema "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/parameters/${id}` };
registry$1.components.parameters.seen.set(parameter, ref);
registry$1.components.parameters.ids.set(id, parameterObject);
if (opts?.manualId) return parameterObject;
return ref;
}
if (opts?.location?.name || opts?.location?.in) return parameterObject;
registry$1.components.parameters.seen.set(parameter, parameterObject);
return parameterObject;
},
addHeader: (header, path, opts) => {
const seenHeader = registry$1.components.headers.seen.get(header);
if (seenHeader) return seenHeader;
const meta = globalRegistry.get(header);
const { id: metaId, ...rest } = meta?.header ?? {};
const id = metaId ?? opts?.manualId;
const headerObject = rest;
if (isRequired(header, "output")) headerObject.required = true;
if (!headerObject.description && meta?.description) headerObject.description = meta.description;
headerObject.schema = registry$1.addSchema(header, [...path, "schema"], {
io: "output",
source: { type: "header" }
});
if (id) {
if (registry$1.components.schemas.ids.has(id)) throw new Error(`Schema "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/headers/${id}` };
registry$1.components.headers.ids.set(id, headerObject);
registry$1.components.headers.seen.set(header, ref);
if (opts?.manualId) return headerObject;
return ref;
}
registry$1.components.headers.seen.set(header, headerObject);
return headerObject;
},
addRequestBody: (requestBody, path, opts) => {
const seenRequestBody = registry$1.components.requestBodies.seen.get(requestBody);
if (seenRequestBody) return seenRequestBody;
const { content, id: metaId, ...rest } = requestBody;
const requestBodyObject = {
...rest,
content: createContent(content, {
registry: registry$1,
io: "input"
}, [...path, "content"])
};
const id = metaId ?? opts?.manualId;
if (id) {
if (registry$1.components.requestBodies.ids.has(id)) throw new Error(`RequestBody "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/requestBodies/${id}` };
registry$1.components.requestBodies.ids.set(id, requestBodyObject);
registry$1.components.requestBodies.seen.set(requestBody, ref);
if (opts?.manualId) return requestBodyObject;
return ref;
}
registry$1.components.requestBodies.seen.set(requestBody, requestBodyObject);
return requestBodyObject;
},
addPathItem: (pathItem, path, opts) => {
const seenPathItem = registry$1.components.pathItems.seen.get(pathItem);
if (seenPathItem) return seenPathItem;
const pathItemObject = {};
const { id: metaId, ...rest } = pathItem;
const id = metaId ?? opts?.manualId;
for (const [key, value] of Object.entries(rest)) {
if (isISpecificationExtension(key)) {
pathItemObject[key] = value;
continue;
}
if (key === "get" || key === "put" || key === "post" || key === "delete" || key === "options" || key === "head" || key === "patch" || key === "trace") {
pathItemObject[key] = createOperation(value, registry$1, [...path, key]);
continue;
}
if (key === "parameters") {
pathItemObject[key] = createManualParameters(value, registry$1, [...path, key]);
continue;
}
pathItemObject[key] = value;
}
if (id) {
if (registry$1.components.pathItems.ids.has(id)) throw new Error(`PathItem "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/pathItems/${id}` };
registry$1.components.pathItems.ids.set(id, pathItemObject);
registry$1.components.pathItems.seen.set(pathItem, ref);
if (opts?.manualId) return pathItemObject;
return ref;
}
registry$1.components.pathItems.seen.set(pathItem, pathItemObject);
return pathItemObject;
},
addResponse: (response, path, opts) => {
const seenResponse = registry$1.components.responses.seen.get(response);
if (seenResponse) return seenResponse;
const { content, headers, links, id: metaId, ...rest } = response;
const responseObject = rest;
const maybeHeaders = createHeaders(headers, registry$1, [...path, "headers"]);
if (maybeHeaders) responseObject.headers = maybeHeaders;
if (content) responseObject.content = createContent(content, {
registry: registry$1,
io: "output"
}, [...path, "content"]);
if (links) responseObject.links = createLinks(links, registry$1, [...path, "links"]);
const id = metaId ?? opts?.manualId;
if (id) {
if (registry$1.components.responses.ids.has(id)) throw new Error(`Response "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/responses/${id}` };
registry$1.components.responses.ids.set(id, responseObject);
registry$1.components.responses.seen.set(response, ref);
if (opts?.manualId) return responseObject;
return ref;
}
registry$1.components.responses.seen.set(response, responseObject);
return responseObject;
},
addCallback: (callback, path, opts) => {
const seenCallback = registry$1.components.callbacks.seen.get(callback);
if (seenCallback) return seenCallback;
const { id: metaId, ...rest } = callback;
const callbackObject = {};
for (const [name, pathItem] of Object.entries(rest)) {
if (isISpecificationExtension(name)) {
callbackObject[name] = pathItem;
continue;
}
callbackObject[name] = registry$1.addPathItem(pathItem, [...path, name]);
}
const id = metaId ?? opts?.manualId;
if (id) {
if (registry$1.components.callbacks.ids.has(id)) throw new Error(`Callback "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/callbacks/${id}` };
registry$1.components.callbacks.ids.set(id, callbackObject);
registry$1.components.callbacks.seen.set(callback, ref);
if (opts?.manualId) return callbackObject;
return ref;
}
registry$1.components.callbacks.seen.set(callback, callbackObject);
return callbackObject;
},
addSecurityScheme: (securityScheme, path, opts) => {
const seenSecurityScheme = registry$1.components.securitySchemes.seen.get(securityScheme);
if (seenSecurityScheme) return seenSecurityScheme;
const { id: metaId, ...rest } = securityScheme;
const securitySchemeObject = rest;
const id = metaId ?? opts?.manualId;
if (id) {
if (registry$1.components.securitySchemes.ids.has(id)) throw new Error(`SecurityScheme "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/securitySchemes/${id}` };
registry$1.components.securitySchemes.ids.set(id, securitySchemeObject);
registry$1.components.securitySchemes.seen.set(securityScheme, ref);
if (opts?.manualId) return securitySchemeObject;
return ref;
}
registry$1.components.securitySchemes.seen.set(securityScheme, securitySchemeObject);
return securitySchemeObject;
},
addLink: (link, path, opts) => {
const seenLink = registry$1.components.links.seen.get(link);
if (seenLink) return seenLink;
const { id: metaId, ...rest } = link;
const linkObject = rest;
const id = metaId ?? opts?.manualId;
if (id) {
if (registry$1.components.links.ids.has(id)) throw new Error(`Link "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/links/${id}` };
registry$1.components.links.ids.set(id, linkObject);
registry$1.components.links.seen.set(link, ref);
if (opts?.manualId) return linkObject;
return ref;
}
registry$1.components.links.seen.set(link, linkObject);
return linkObject;
},
addExample: (example, path, opts) => {
const seenExample = registry$1.components.examples.seen.get(example);
if (seenExample) return seenExample;
const { id: metaId, ...rest } = example;
const exampleObject = rest;
const id = metaId ?? opts?.manualId;
if (id) {
if (registry$1.components.examples.ids.has(id)) throw new Error(`Example "${id}" at ${path.join(" > ")} is already registered`);
const ref = { $ref: `#/components/examples/${id}` };
registry$1.components.examples.ids.set(id, exampleObject);
registry$1.components.examples.seen.set(example, ref);
if (opts?.manualId) return exampleObject;
return ref;
}
registry$1.components.examples.seen.set(example, exampleObject);
return exampleObject;
}
};
registerSchemas(components?.schemas, registry$1);
registerParameters(components?.parameters, registry$1);
registerHeaders(components?.headers, registry$1);
registerResponses(components?.responses, registry$1);
registerPathItems(components?.pathItems, registry$1);
registerRequestBodies(components?.requestBodies, registry$1);
registerCallbacks(components?.callbacks, registry$1);
registerSecuritySchemes(components?.securitySchemes, registry$1);
registerLinks(components?.links, registry$1);
registerExamples(components?.examples, registry$1);
return registry$1;
};
const registerSchemas = (schemas, registry$1) => {
if (!schemas) return;
for (const [key, schema] of Object.entries(schemas)) {
if (isAnyZodType(schema)) {
const id = globalRegistry.get(schema)?.id ?? key;
registry$1.components.schemas.manual.set(id, {
input: { schemaObject: {} },
output: { schemaObject: {} },
zodType: schema
});
continue;
}
registry$1.components.schemas.ids.set(key, schema);
}
};
const registerParameters = (parameters, registry$1) => {
if (!parameters) return;
for (const [key, schema] of Object.entries(parameters)) {
if (isAnyZodType(schema)) {
const path = [
"components",
"parameters",
key
];
registry$1.addParameter(schema, path, { manualId: key });
continue;
}
registry$1.components.parameters.ids.set(key, schema);
}
};
const registerHeaders = (headers, registry$1) => {
if (!headers) return;
for (const [key, schema] of Object.entries(headers)) {
if (isAnyZodType(schema)) {
const path = [
"components",
"headers",
key
];
registry$1.addHeader(schema, path, { manualId: key });
continue;
}
registry$1.components.headers.ids.set(key, schema);
}
};
const registerResponses = (responses, registry$1) => {
if (!responses) return;
for (const [key, schema] of Object.entries(responses)) {
const responseObject = registry$1.addResponse(schema, [
"components",
"responses",
key
], { manualId: key });
registry$1.components.responses.ids.set(key, responseObject);
registry$1.components.responses.seen.set(schema, responseObject);
}
};
const registerRequestBodies = (requestBodies, registry$1) => {
if (!requestBodies) return;
for (const [key, schema] of Object.entries(requestBodies)) {
if (isAnyZodType(schema)) {
registry$1.addRequestBody(schema, [
"components",
"requestBodies",
key
], { manualId: key });
continue;
}
registry$1.components.requestBodies.ids.set(key, schema);
}
};
const registerCallbacks = (callbacks, registry$1) => {
if (!callbacks) return;
for (const [key, schema] of Object.entries(callbacks)) registry$1.addCallback(schema, [
"components",
"callbacks",
key
], { manualId: key });
};
const registerPathItems = (pathItems, registry$1) => {
if (!pathItems) return;
for (const [key, schema] of Object.entries(pathItems)) registry$1.addPathItem(schema, [
"components",
"pathItems",
key
], { manualId: key });
};
const registerSecuritySchemes = (securitySchemes, registry$1) => {
if (!securitySchemes) return;
for (const [key, schema] of Object.entries(securitySchemes)) registry$1.addSecurityScheme(schema, [
"components",
"securitySchemes",
key
], { manualId: key });
};
const registerLinks = (links, registry$1) => {
if (!links) return;
for (const [key, schema] of Object.entries(links)) registry$1.addLink(schema, [
"components",
"links",
key
], { manualId: key });
};
const registerExamples = (examples, registry$1) => {
if (!examples) return;
for (const [key, schema] of Object.entries(examples)) registry$1.components.examples.ids.set(key, schema);
};
const createIOSchemas = (ctx) => {
const { schemas, components, manual } = createSchemas(Object.fromEntries(ctx.registry.components.schemas[ctx.io]), ctx);
for (const [key, schema] of Object.entries(components)) ctx.registry.components.schemas.ids.set(key, schema);
for (const [key, schema] of Object.entries(schemas)) {
const ioSchema = ctx.registry.components.schemas[ctx.io].get(key);
if (ioSchema) Object.assign(ioSchema.schemaObject, schema);
}
for (const [key, value] of Object.entries(manual)) {
const manualSchema = ctx.registry.components.schemas.manual.get(key);
if (!manualSchema) continue;
if (components[key]) manualSchema[ctx.io].used = true;
Object.assign(manualSchema[ctx.io].schemaObject, value);
}
};
const createManualSchemas = (registry$1) => {
for (const [key, value] of registry$1.components.schemas.manual) if (!value.input.used) {
const schema = value[globalRegistry.get(value.zodType)?.unusedIO ?? "output"].schemaObject;
registry$1.components.schemas.ids.set(key, schema);
}
};
const createComponents = (registry$1, opts, openapiVersion) => {
createIOSchemas({
registry: registry$1,
io: "input",
opts,
openapiVersion
});
createIOSchemas({
registry: registry$1,
io: "output",
opts,
openapiVersion
});
createManualSchemas(registry$1);
const components = {};
if (registry$1.components.schemas.ids.size > 0) components.schemas = Object.fromEntries(registry$1.components.schemas.ids);
if (registry$1.components.headers.ids.size > 0) components.headers = Object.fromEntries(registry$1.components.headers.ids);
if (registry$1.components.requestBodies.ids.size > 0) components.requestBodies = Object.fromEntries(registry$1.components.requestBodies.ids);
if (registry$1.components.responses.ids.size > 0) components.responses = Object.fromEntries(registry$1.components.responses.ids);
if (registry$1.components.parameters.ids.size > 0) components.parameters = Object.fromEntries(registry$1.components.parameters.ids);
if (registry$1.components.callbacks.ids.size > 0) components.callbacks = Object.fromEntries(registry$1.components.callbacks.ids);
if (registry$1.components.pathItems.ids.size > 0) components.pathItems = Object.fromEntries(registry$1.components.pathItems.ids);
if (registry$1.components.securitySchemes.ids.size > 0) components.securitySchemes = Object.fromEntries(registry$1.components.securitySchemes.ids);
if (registry$1.components.links.ids.size > 0) components.links = Object.fromEntries(registry$1.components.links.ids);
if (registry$1.components.examples.ids.size > 0) components.examples = Object.fromEntries(registry$1.components.examples.ids);
return components;
};
//#endregion
export { unwrapZodObject as a, createPaths as i, createRegistry as n, isAnyZodType as o, createSchema as r, createComponents as t };