UNPKG

dilswer

Version:

Blazingly fast data validation library with TypeScript integration.

396 lines (394 loc) 12.2 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/json-schema-parser/to-json-schema.ts import { BaseType } from "../data-types/base-type.mjs"; import { Type } from "../data-types/Type.mjs"; import { TypeKindNames } from "../data-types/type-kind-names.mjs"; import { isDefined } from "./is-defined.mjs"; import { NameGenerator } from "../ts-type-generator/name-generator.mjs"; var DataTypeJsonSchemaGenerator = class { constructor(options) { __publicField(this, "options", options); __publicField(this, "incompatibleTypes"); __publicField(this, "customParser"); __publicField(this, "definitionNames", /* @__PURE__ */ new Map()); __publicField(this, "definitions", /* @__PURE__ */ new Map()); var _a, _b; this.incompatibleTypes = (_a = options.incompatibleTypes) != null ? _a : "throw"; this.customParser = (_b = options.customParser) != null ? _b : {}; } throwIncompatibleTypeError(typeName) { throw new Error( `Cannot parse type "${typeName}" to JSON Schema. Incompatible types.` ); } assignMetadataToSchema(schema, type) { const meta = BaseType.getOriginalMetadata(type); if (meta.title) schema.title = meta.title; if (meta.description) schema.description = meta.description; if (meta.format) schema.format = meta.format; } parsePrimitive(type) { var _a, _b, _c; let schema = {}; this.assignMetadataToSchema(schema, type); switch (type.simpleType) { case "boolean": schema.type = "boolean"; break; case "integer": schema.type = "integer"; if (type.options.max != null) { schema.maximum = type.options.max; } if (type.options.min != null) { schema.minimum = type.options.min; } break; case "null": schema.type = "null"; break; case "number": schema.type = "number"; if (type.options.max != null) { schema.maximum = type.options.max; } if (type.options.min != null) { schema.minimum = type.options.min; } break; case "string": schema.type = "string"; if (type.options.max != null) { schema.maxLength = type.options.max; } if (type.options.min != null) { schema.minLength = type.options.min; } break; case "stringinteger": schema.type = "string"; schema.pattern = "^-?[0-9]+$"; break; case "stringnumeral": schema.type = "string"; schema.pattern = "^-?[0-9]+(\\.[0-9]+)?$"; break; case "unknown": break; case "function": if (this.customParser.Function) { schema = this.customParser.Function(type, this.options); break; } if (this.incompatibleTypes === "throw") { this.throwIncompatibleTypeError(type.simpleType); } if (this.incompatibleTypes === "omit") { schema = void 0; break; } (_a = schema.title) != null ? _a : schema.title = "Function"; break; case "symbol": if (this.customParser.Symbol) { schema = this.customParser.Symbol(type, this.options); break; } if (this.incompatibleTypes === "throw") { this.throwIncompatibleTypeError(type.simpleType); } if (this.incompatibleTypes === "omit") { schema = void 0; break; } (_b = schema.title) != null ? _b : schema.title = "Symbol"; break; case "undefined": if (this.customParser.Undefined) { schema = this.customParser.Undefined(type, this.options); break; } if (this.incompatibleTypes === "throw") { this.throwIncompatibleTypeError(type.simpleType); } if (this.incompatibleTypes === "omit") { schema = void 0; break; } (_c = schema.title) != null ? _c : schema.title = "undefined"; break; } return schema; } parseArrayOf(type, children) { const schema = { type: "array", items: children == null ? void 0 : children.filter(isDefined) }; this.assignMetadataToSchema(schema, type); return schema; } parseTuple(type, children) { var _a; const items = (_a = children == null ? void 0 : children.map((c) => c != null ? c : {})) != null ? _a : []; const schema = { type: "array", items, required: items.map((_, i) => i.toString()), minItems: items.length, maxItems: items.length }; this.assignMetadataToSchema(schema, type); return schema; } parseRecordOf(type, children = []) { const schema = { type: "object", properties: {}, required: [] }; this.assignMetadataToSchema(schema, type); for (let i = 0; i < children.length; i++) { const { child, propertyName, required } = children[i]; if (child == null) continue; Object.assign(schema.properties, { [propertyName]: child }); if (required) { schema.required.push(propertyName); } } if (typeof this.options.additionalProperties === "boolean") { schema.additionalProperties = this.options.additionalProperties; } return schema; } parseDict(type, children) { const schema = { type: "object", additionalProperties: { anyOf: children == null ? void 0 : children.filter(isDefined) } }; this.assignMetadataToSchema(schema, type); return schema; } parseSetOf(type, children = []) { if (this.customParser.Set) { return this.customParser.Set( children.filter(isDefined), type, this.options ); } if (this.incompatibleTypes === "throw") { this.throwIncompatibleTypeError("Set"); } if (this.incompatibleTypes === "omit") return void 0; const schema = { title: "Set" }; this.assignMetadataToSchema(schema, type); return schema; } parseOneOf(type, children) { const schema = { anyOf: children.filter(isDefined) }; this.assignMetadataToSchema(schema, type); return schema; } parseAllOf(type, children) { const schema = { allOf: children.filter(isDefined) }; this.assignMetadataToSchema(schema, type); return schema; } parseLiteral(type) { const schema = {}; this.assignMetadataToSchema(schema, type); if (typeof type.literal === "string") { schema.type = "string"; schema.enum = [type.literal]; return schema; } if (typeof type.literal === "number") { schema.type = "number"; schema.enum = [type.literal]; return schema; } if (typeof type.literal === "boolean") { schema.type = "boolean"; schema.enum = [type.literal]; return schema; } throw new Error( "Invalid literal type, literals can be only of string, number or boolean type." ); } parseEnum(type) { const members = Object.entries(type.enumInstance); const schema = { anyOf: [] }; for (const [key, member] of members) { const subSchema = this.parseEnumMember( Type.EnumMember(member) ); subSchema.title = key; schema.anyOf.push(subSchema); } this.assignMetadataToSchema(schema, type); return schema; } parseEnumMember(type) { const schema = {}; this.assignMetadataToSchema(schema, type); if (typeof type.enumMember === "string") { schema.type = "string"; schema.enum = [type.enumMember]; return schema; } if (typeof type.enumMember === "number") { schema.type = "number"; schema.enum = [type.enumMember]; return schema; } throw new Error( "Invalid enum member type, enum members can be only of string or number type." ); } parseInstanceOf(type) { if (this.customParser.InstanceOf) { return this.customParser.InstanceOf(type, this.options); } const constructor = type.instanceOf; const schema = { type: "object", title: constructor.name }; this.assignMetadataToSchema(schema, type); return schema; } parseCustom(type) { if (this.customParser.Custom) { return this.customParser.Custom(type.custom, type, this.options); } if (this.incompatibleTypes === "throw") { this.throwIncompatibleTypeError("Custom Validator"); } if (this.incompatibleTypes === "omit") return void 0; const schema = { title: `Custom Validator (${type.custom.name || "anonymous"})` }; this.assignMetadataToSchema(schema, type); return schema; } parseStringMatching(type) { const schema = { type: "string", pattern: type.pattern.source }; if (type.pattern.flags) { Object.assign(schema, { patternFlags: type.pattern.flags }); } this.assignMetadataToSchema(schema, type); return schema; } parseCircular(circular, children) { const [childSchema] = children; const child = circular.type; if (this.definitionNames.has(child)) { const definitionName = this.definitionNames.get(child); this.definitions.set(definitionName, childSchema != null ? childSchema : { type: "null" }); return { allOf: [ { $ref: `#/definitions/${definitionName}` } ] }; } return childSchema; } parseCircularRef(type) { var _a; const referencedType = type._getReferencedType(); if (this.definitionNames.has(referencedType)) { return { $ref: `#/definitions/${this.definitionNames.get(referencedType)}` }; } const metadata = BaseType.getOriginalMetadata(referencedType); const title = metadata.title; if (title) { const typeName2 = NameGenerator.generate(title); this.definitionNames.set(referencedType, typeName2); return { $ref: `#/definitions/${typeName2}` }; } const typeName = NameGenerator.generate( (_a = TypeKindNames.get(referencedType.kind)) != null ? _a : "Circular" ); this.definitionNames.set(referencedType, typeName); return { $ref: `#/definitions/${typeName}` }; } visit(type, children) { switch (type.kind) { case "simple": return this.parsePrimitive(type); case "array": return this.parseArrayOf(type, children); case "tuple": return this.parseTuple(type, children); case "record": return this.parseRecordOf(type, children); case "dictionary": return this.parseDict(type, children); case "set": return this.parseSetOf(type, children); case "union": return this.parseOneOf(type, children); case "intersection": return this.parseAllOf(type, children); case "literal": return this.parseLiteral(type); case "enumUnion": return this.parseEnum(type); case "enumMember": return this.parseEnumMember(type); case "instanceOf": return this.parseInstanceOf(type); case "custom": return this.parseCustom(type); case "stringMatching": return this.parseStringMatching(type); case "circular": return this.parseCircular(type, children); case "circularRef": return this.parseCircularRef(type); } } }; var toJsonSchema = (type, options = {}, include$schemaProperty = true) => { try { const visitor = new DataTypeJsonSchemaGenerator(options); const schema = type._acceptVisitor(visitor); if (schema && visitor.definitions.size > 0) { schema.definitions = Object.fromEntries(visitor.definitions.entries()); } if (include$schemaProperty && schema) { schema.$schema = "http://json-schema.org/draft-06/schema#"; } return schema; } finally { NameGenerator.clear(); } }; export { toJsonSchema };