@doubter/json-schema
Version:
Converts Doubter shapes from and to JSON schemas.
398 lines (394 loc) • 14.4 kB
JavaScript
'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;