UNPKG

@tsed/schema

Version:
156 lines (123 loc) 4.12 kB
import {getValue, isFunction, isObject} from "@tsed/core"; import {mapAliasedProperties} from "../../domain/JsonAliasMap.js"; import {JsonSchema} from "../../domain/JsonSchema.js"; import {SpecTypes} from "../../domain/SpecTypes.js"; import {alterOneOf} from "../../hooks/alterOneOf.js"; import {JsonSchemaOptions} from "../../interfaces/JsonSchemaOptions.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: string[] = []; /** * @ignore */ function isEmptyProperties(key: string, value: any) { return typeof value === "object" && ["items", "properties", "additionalProperties"].includes(key) && Object.keys(value).length === 0; } /** * @ignore */ function shouldMapAlias(key: string, value: any, useAlias: boolean) { return typeof value === "object" && useAlias && ["properties", "additionalProperties"].includes(key); } /** * @ignore */ function shouldSkipKey(key: string, {specType = SpecTypes.JSON, customKeys = false}: JsonSchemaOptions) { return ( IGNORES.includes(key) || (key.startsWith("#") && (customKeys === false || specType !== SpecTypes.JSON)) || (specType !== SpecTypes.JSON && IGNORES_OPENSPEC.includes(key)) ); } function isExample(key: string, value: any, options: JsonSchemaOptions) { return key === "examples" && isObject(value) && [SpecTypes.OPENAPI, SpecTypes.ASYNCAPI].includes(options.specType!)!; } function mapOptions(options: JsonSchemaOptions) { 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: JsonSchema, options: JsonSchemaOptions) { const {useAlias} = options; return [...schema.keys()] .filter((key) => !shouldSkipKey(key, options)) .reduce((item: any, 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: JsonSchema, options: JsonSchemaOptions) { let obj: any = mapKeys(schema, options); if (schema.isClass) { obj = execMapper("inheritedClass", [obj], { ...options, root: false, target: schema.getComputedType() }); } obj = execMapper("generics", [obj], { ...options, root: false } as any); if (schema.has(options.specType as string)) { obj = { ...obj, ...schema.get(options.specType as string).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); return obj; } export function schemaMapper(schema: JsonSchema, opts: JsonSchemaOptions): any { 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);