UNPKG

@doubter/json-schema

Version:
398 lines (394 loc) 14.4 kB
'use strict'; var doubter = require('doubter'); function toJSONSchema(source, options) { if (options === void 0) { options = {}; } var definitionShapes = options.definitions, _a = options.definitionsKey, definitionsKey = _a === void 0 ? 'definitions' : _a, _b = options.basePath, basePath = _b === void 0 ? '#' : _b, unusedDefinitions = options.unusedDefinitions, _c = options.constAsEnum, constAsEnum = _c === void 0 ? false : _c, dialect = options.dialect, _d = options.postprocess, postprocess = _d === void 0 ? applyAnnotations : _d; var converter = new Converter(basePath + '/' + definitionsKey + '/'); converter.constAsEnum = constAsEnum; converter.postprocess = postprocess; if (definitionShapes !== undefined) { for (var name_1 in definitionShapes) { converter.registerDefinition(name_1, definitionShapes[name_1]); } } var schema; if (source instanceof doubter.Shape) { schema = converter.convert(source); } else { schema = {}; for (var name_2 in source) { converter.registerDefinition(name_2, source[name_2]); } for (var name_3 in source) { converter.convert(source[name_3]); } } if (definitionShapes !== undefined && unusedDefinitions) { for (var name_4 in definitionShapes) { converter.convert(definitionShapes[name_4]); } } converter.results.forEach(function (result) { (schema[definitionsKey] || (schema[definitionsKey] = {}))[result.name] = result.schema; }); return dialect === undefined ? schema : Object.assign({ $schema: dialect }, schema); } var Converter = /** @class */ (function () { function Converter( /** * The path prefix for schema references. */ definitionsPath) { this.definitionsPath = definitionsPath; this.namelessPrefix = 'shape'; this.constAsEnum = false; this.postprocess = applyAnnotations; /** * The named conversion results. */ this.results = new Map(); /** * Map from a shape to a definition name. */ this._shapeNames = new Map(); /** * Occupied definition names. */ this._names = new Set(); /** * The suffix added to definition names created for cyclic shapes that aren't among definitions. */ this._namelessCounter = 0; /** * The stack of shapes to detect the cyclic shapes dependencies. */ this._shapeStack = new Set(); } Converter.prototype.registerDefinition = function (name, shape) { this._shapeNames.set(shape, name); this._names.add(name); }; Converter.prototype.convert = function (shape) { var _a = this, results = _a.results, definitionsPath = _a.definitionsPath, _shapeStack = _a._shapeStack, _names = _a._names; var result = results.get(shape); var name = this._shapeNames.get(shape); if (name !== undefined) { // Registered definition if (result !== undefined) { return { $ref: definitionsPath + name }; } result = { schema: null, name: name }; results.set(shape, result); } else if (_shapeStack.has(shape)) { // Cyclic dependency if (result !== undefined) { return { $ref: definitionsPath + result.name }; } do { name = this.namelessPrefix + ++this._namelessCounter; } while (_names.has(name)); _names.add(name); result = { schema: null, name: name }; results.set(shape, result); return { $ref: definitionsPath + name }; } _shapeStack.add(shape); var schema = convertShape(shape, this); _shapeStack.delete(shape); result || (result = results.get(shape)); if (result !== undefined) { result.schema = schema; return { $ref: definitionsPath + result.name }; } return schema; }; return Converter; }()); function convertShape(shape, converter) { if (!(shape instanceof doubter.Shape)) { throw new Error('Expected a shape'); } if (shape instanceof doubter.ConvertShape) ; else if (shape instanceof doubter.CatchShape) { return converter.convert(shape.baseShape); } else if (shape instanceof doubter.LazyShape) { return converter.convert(shape.providedShape); } else if (shape instanceof doubter.PipeShape) { return converter.convert(shape.inputShape); } var schema; if (shape instanceof doubter.ReplaceShape) { schema = convertReplaceShape(shape, converter); } else if (shape instanceof doubter.DenyShape) { schema = convertDenyShape(shape, converter); } else if (shape instanceof doubter.ExcludeShape) { schema = convertExcludeShape(shape, converter); } else if (shape instanceof doubter.NumberShape) { schema = convertNumberShape(shape); } else if (shape instanceof doubter.NeverShape) { schema = { not: {} }; } else if (shape instanceof doubter.BigIntShape) { schema = { type: 'integer', format: 'int64' }; } else if (shape instanceof doubter.StringShape) { schema = convertStringShape(shape); } else if (shape instanceof doubter.EnumShape) { schema = { enum: shape.values.slice(0) }; } else if (shape instanceof doubter.UnionShape) { schema = { anyOf: convertShapes(shape.shapes, converter) }; } else if (shape instanceof doubter.ObjectShape) { schema = convertObjectShape(shape, converter); } else if (shape instanceof doubter.RecordShape) { schema = convertRecordShape(shape, converter); } else if (shape instanceof doubter.ConstShape) { schema = convertConstShape(shape, converter); } else if (shape instanceof doubter.BooleanShape) { schema = { type: 'boolean' }; } else if (shape instanceof doubter.PromiseShape) { schema = { type: 'object' }; } else if (shape instanceof doubter.ArrayShape) { schema = convertArrayShape(shape, converter); } else if (shape instanceof doubter.SetShape) { schema = convertSetShape(shape, converter); } else if (shape instanceof doubter.MapShape) { schema = convertMapShape(shape, converter); } else if (shape instanceof doubter.DateShape) { schema = { type: 'string', format: 'date-time' }; } else if (shape instanceof doubter.IntersectionShape) { schema = { allOf: convertShapes(shape.shapes, converter) }; } else if (shape instanceof doubter.InstanceShape) { schema = { type: 'object' }; } else { schema = {}; } converter.postprocess(shape, schema); return schema; } function convertShapes(shapes, converter) { var schemas = []; for (var _i = 0, shapes_1 = shapes; _i < shapes_1.length; _i++) { var shape = shapes_1[_i]; schemas.push(converter.convert(shape)); } return schemas; } function convertReplaceShape(shape, converter) { if (shape.inputValue === undefined) { return converter.convert(shape.baseShape); } return { oneOf: [ shape.inputValue === null ? { type: 'null' } : createConstSchema(shape.inputValue, converter), converter.convert(shape.baseShape), ], }; } function convertDenyShape(shape, converter) { var schema = converter.convert(shape.baseShape); schema.not = createConstSchema(shape.deniedValue, converter); return schema; } function convertExcludeShape(shape, converter) { var schema = converter.convert(shape.baseShape); schema.not = converter.convert(shape.excludedShape); return schema; } function convertConstShape(shape, converter) { if (shape.value === null || shape.value === undefined) { return { type: 'null' }; } return createConstSchema(shape.value, converter); } function convertArrayShape(shape, converter) { var schema = { type: 'array' }; schema.items = shape.restShape !== null ? converter.convert(shape.restShape) : false; if (shape.headShapes !== null && shape.headShapes.length !== 0) { schema.prefixItems = convertShapes(shape.headShapes, converter); } for (var _i = 0, _a = shape.operations; _i < _a.length; _i++) { var _b = _a[_i], type = _b.type, param = _b.param; switch (type) { case 'array.max': if (schema.maxItems === undefined || param < schema.maxItems) { schema.maxItems = param; } break; case 'array.min': if (schema.minItems === undefined || param > schema.minItems) { schema.minItems = param; } break; case 'array.includes': var paramSchema = param instanceof doubter.Shape ? convertShape(shape, converter) : createConstSchema(param, converter); if (schema.contains === undefined) { schema.contains = paramSchema; } else { (schema.allOf || (schema.allOf = [])).push({ type: 'array', contains: paramSchema }); } break; } } return schema; } function convertSetShape(shape, converter) { var schema = { type: 'array', uniqueItems: true }; schema.items = converter.convert(shape.valueShape); for (var _i = 0, _a = shape.operations; _i < _a.length; _i++) { var _b = _a[_i], type = _b.type, param = _b.param; switch (type) { case 'set.max': if (schema.maxItems === undefined || param < schema.maxItems) { schema.maxItems = param; } break; case 'set.min': if (schema.minItems === undefined || param > schema.minItems) { schema.minItems = param; } break; } } return schema; } function convertMapShape(shape, converter) { return { type: 'array', items: { type: 'array', prefixItems: [converter.convert(shape.keyShape), converter.convert(shape.valueShape)], items: false, }, }; } function convertObjectShape(shape, converter) { var properties; var required; var schema = { type: 'object' }; for (var _i = 0, _a = shape.keys; _i < _a.length; _i++) { var key = _a[_i]; var valueShape = shape.propShapes[key]; if (!valueShape.accepts(undefined)) { (required || (required = [])).push(key); } (properties || (properties = {}))[key] = converter.convert(valueShape); } if (properties !== undefined) { schema.properties = properties; } if (required !== undefined) { schema.required = required; } if (shape.restShape !== null) { schema.additionalProperties = converter.convert(shape.restShape); } else if (shape.keysMode === 'exact') { schema.additionalProperties = false; } return schema; } function convertNumberShape(shape) { var schema = { type: 'number' }; for (var _i = 0, _a = shape.operations; _i < _a.length; _i++) { var _b = _a[_i], type = _b.type, param = _b.param; switch (type) { case 'number.int': schema.type = 'integer'; break; case 'number.lte': if (schema.maximum === undefined || param > schema.maximum) { schema.maximum = param; } break; case 'number.gte': if (schema.minimum === undefined || param < schema.minimum) { schema.minimum = param; } break; case 'number.lt': if (schema.exclusiveMaximum === undefined || param > schema.exclusiveMaximum) { schema.exclusiveMaximum = param; } break; case 'number.gt': if (schema.exclusiveMinimum === undefined || param < schema.exclusiveMinimum) { schema.exclusiveMinimum = param; } break; case 'number.multipleOf': schema.multipleOf = param; break; } } return schema; } function convertStringShape(shape) { var schema = { type: 'string' }; for (var _i = 0, _a = shape.operations; _i < _a.length; _i++) { var _b = _a[_i], type = _b.type, param = _b.param; switch (type) { case 'string.max': if (schema.maxLength === undefined || param < schema.maxLength) { schema.maxLength = param; } break; case 'string.min': if (schema.minLength === undefined || param > schema.minLength) { schema.minLength = param; } break; case 'string.regex': if (schema.pattern === undefined) { schema.pattern = param.toString(); } else { (schema.allOf || (schema.allOf = [])).push({ type: 'string', pattern: param }); } break; } } return schema; } function convertRecordShape(shape, converter) { var schema = { additionalProperties: converter.convert(shape.valuesShape) }; if (shape.keysShape !== null) { schema.propertyNames = converter.convert(shape.keysShape); } return schema; } function createConstSchema(value, converter) { return converter.constAsEnum ? { enum: [value] } : { const: value }; } function applyAnnotations(shape, schema) { var _a = shape.annotations, title = _a.title, description = _a.description; if (typeof title === 'string') { schema.title = title; } if (typeof description === 'string') { schema.description = description; } } exports.toJSONSchema = toJSONSchema;