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