UNPKG

@tsed/schema

Version:
122 lines (121 loc) 4.09 kB
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);