@tsed/schema
Version:
JsonSchema module for Ts.ED Framework
122 lines (121 loc) • 4.09 kB
JavaScript
import { getValue, isFunction, isObject } from "@tsed/core";
import { mapAliasedProperties } from "../../domain/JsonAliasMap.js";
import { SpecTypes } from "../../domain/SpecTypes.js";
import { alterOneOf } from "../../hooks/alterOneOf.js";
import { execMapper, hasMapper, registerJsonSchemaMapper } from "../../registries/JsonSchemaMapperContainer.js";
/**
* @ignore
*/
const IGNORES = ["name", "$required", "$hooks", "_nestedGenerics", SpecTypes.OPENAPI, SpecTypes.SWAGGER, SpecTypes.JSON];
/**
* @ignore
*/
const IGNORES_OPENSPEC = [];
/**
* @ignore
* TODO check if we can remove this
*/
function isEmptyProperties(key, value) {
return typeof value === "object" && ["items", "properties", "additionalProperties"].includes(key) && Object.keys(value).length === 0;
}
/**
* @ignore
*/
function shouldMapAlias(key, value, useAlias) {
return typeof value === "object" && useAlias && ["properties", "additionalProperties"].includes(key);
}
/**
* @ignore
*/
function shouldSkipKey(key, { specType = SpecTypes.JSON, customKeys = false }) {
return (IGNORES.includes(key) ||
key.startsWith("x-") ||
(key.startsWith("#") && (!customKeys || specType !== SpecTypes.JSON)) ||
(specType !== SpecTypes.JSON && IGNORES_OPENSPEC.includes(key)));
}
function isExample(key, value, options) {
return key === "examples" && isObject(value) && [SpecTypes.OPENAPI, SpecTypes.ASYNCAPI].includes(options.specType);
}
function mapOptions(options) {
let addDef = false;
if (!options) {
addDef = true;
options = { components: { schemas: {} }, inlineEnums: true };
}
const { useAlias = true, components = { schemas: {} } } = options;
options = {
...options,
useAlias,
components
};
return {
addDef,
options
};
}
function mapKeys(schema, options) {
const { useAlias } = options;
return [...schema.keys()]
.filter((key) => !shouldSkipKey(key, options))
.reduce((item, key) => {
let value = schema.get(key);
key = key.replace(/^#/, "");
if (key === "type") {
item[key] = schema.getJsonType();
return item;
}
if (isExample(key, value, options)) {
key = "example";
value = Object.values(value)[0];
}
if (value && typeof value === "object" && hasMapper(key)) {
value = execMapper(key, [value], options, schema);
if (isEmptyProperties(key, value)) {
return item;
}
if (shouldMapAlias(key, value, useAlias)) {
value = mapAliasedProperties(value, schema.alias);
}
}
item[key] = value;
return item;
}, {});
}
function serializeSchema(schema, options) {
let obj = mapKeys(schema, options);
if (schema.isClass && !schema.isLocalSchema) {
obj = execMapper("inheritedClass", [obj], {
...options,
root: false,
target: schema.class
});
}
if (schema.has(options.specType)) {
obj = {
...obj,
...schema.get(options.specType).toJSON(options)
};
}
if (isFunction(obj.default)) {
obj.default = obj.default();
}
obj = execMapper("required", [obj, schema], options);
obj = execMapper("nullable", [obj, schema], options);
obj = alterOneOf(obj, schema, options);
obj = execMapper("enums", [obj, schema], options);
obj = execMapper("discriminatorMapping", [obj, schema], options);
obj = execMapper("generics", [obj, schema], options);
if (obj.type === "array" && !obj.items && !obj.contains) {
obj.items = {};
}
return obj;
}
export function schemaMapper(schema, opts) {
const { options, addDef } = mapOptions(opts);
const obj = serializeSchema(schema, options);
if (addDef && Object.keys(getValue(options, "components.schemas", {})).length) {
obj.definitions = options.components.schemas;
}
return obj;
}
registerJsonSchemaMapper("schema", schemaMapper);