UNPKG

tynder

Version:

TypeScript friendly Data validator for JavaScript.

331 lines 12.4 kB
"use strict"; // Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln Object.defineProperty(exports, "__esModule", { value: true }); exports.generateJsonSchema = exports.generateJsonSchemaObject = void 0; function addMetaInfo(a, ty) { const a2 = Object.assign({}, a); let changed = false; if (ty.docComment) { a2.description = ty.docComment; changed = true; } switch (ty.kind) { case 'repeated': if (typeof ty.min === 'number') { a2.minItems = ty.min; changed = true; } if (typeof ty.max === 'number') { a2.maxItems = ty.max; changed = true; } break; case 'primitive': if (typeof ty.minValue === 'number') { a2.minimum = ty.minValue; changed = true; } if (typeof ty.maxValue === 'number') { a2.maximum = ty.maxValue; changed = true; } if (typeof ty.greaterThanValue === 'number') { a2.exclusiveMinimum = ty.greaterThanValue; changed = true; } if (typeof ty.lessThanValue === 'number') { a2.exclusiveMaximum = ty.lessThanValue; changed = true; } if (typeof ty.minLength === 'number') { a2.minLength = ty.minLength; changed = true; } if (typeof ty.maxLength === 'number') { a2.maxLength = ty.maxLength; changed = true; } if (ty.pattern) { a2.pattern = ty.pattern.source; changed = true; } break; } return (changed ? a2 : a); } function generateJsonSchemaInner(schema, ty, nestLevel) { var _a; if (0 < nestLevel && ty.typeName) { const ret = { $ref: `#/definitions/${ty.typeName.replace(/\./g, '/properties/')}`, }; const r2 = addMetaInfo(ret, ty); if (ret !== r2) { // NOTE: `$ref` cannot have value constraints. return generateJsonSchemaInner(schema, ty, 0); } else { return ret; } } switch (ty.kind) { case 'symlink': { const ret = { $ref: `#/definitions/${ty.symlinkTargetName}`, }; const r2 = addMetaInfo(ret, ty); if (ret !== r2) { // NOTE: `$ref` cannot have value constraints. const t2 = (_a = schema.get(ty.symlinkTargetName)) === null || _a === void 0 ? void 0 : _a.ty; if (t2) { return generateJsonSchemaInner(schema, t2, 0); } else { // Drop constraints. return ret; } } else { return ret; } } case 'repeated': { const ret = { type: 'array', items: generateJsonSchemaInner(schema, ty.repeated, nestLevel + 1), }; if (typeof ty.min === 'number') { ret.minItems = ty.min; } if (typeof ty.max === 'number') { ret.maxItems = ty.max; } return addMetaInfo(ret, ty); } case 'sequence': { const ret = { type: 'array', items: { anyOf: ty.sequence.map(x => generateJsonSchemaInner(schema, x, nestLevel + 1)) }, }; return addMetaInfo(ret, ty); } case 'spread': { return generateJsonSchemaInner(schema, ty.spread, nestLevel + 1); } case 'one-of': { const ret = { anyOf: ty.oneOf.map(x => generateJsonSchemaInner(schema, x, nestLevel + 1)), }; return addMetaInfo(ret, ty); } case 'optional': { const ret = { oneOf: [ generateJsonSchemaInner(schema, ty.optional, nestLevel + 1), { type: 'null' }, ], }; return addMetaInfo(ret, ty); } case 'enum': { const ret = { type: ['string', 'number'], enum: ty.values.map(x => x[1]), }; return addMetaInfo(ret, ty); } case 'object': { const properties = {}; const patternProperties = {}; let patternPropsCount = 0; const required = []; for (const m of ty.members) { const z = generateJsonSchemaInner(schema, m[1].kind === 'optional' ? m[1].optional : m[1], nestLevel + 1); if (m[3]) { z.description = m[3]; } else { delete z.description; } properties[m[0]] = z; if (m[1].kind !== 'optional') { required.push(m[0]); } } for (const m of ty.additionalProps || []) { const z = generateJsonSchemaInner(schema, m[1], nestLevel + 1); if (m[3]) { z.description = m[3]; } else { delete z.description; } for (const k of m[0]) { patternPropsCount++; switch (k) { case 'number': patternProperties['^[0-9]+$'] = z; break; case 'string': patternProperties['^.*$'] = z; break; default: patternProperties[k.source] = z; break; } } } const ret = Object.assign(Object.assign(Object.assign({ type: 'object', properties }, (0 < patternPropsCount ? { patternProperties } : {})), (0 < required.length ? { required } : {})), { additionalProperties: false }); return addMetaInfo(ret, ty); } case 'primitive': { switch (ty.primitiveName) { case 'null': case 'undefined': { const ret = { type: 'null', }; return addMetaInfo(ret, ty); } case 'number': { const ret = { type: 'number', }; return addMetaInfo(ret, ty); } case 'bigint': { const ret = { type: ['integer', 'string'], }; return addMetaInfo(ret, ty); } case 'integer': { const ret = { type: 'integer', }; return addMetaInfo(ret, ty); } case 'string': { const ret = { type: 'string', }; return addMetaInfo(ret, ty); } case 'boolean': { const ret = { type: 'boolean', }; return addMetaInfo(ret, ty); } } // TODO: Function, DateStr, DateTimeStr } case 'primitive-value': { switch (typeof ty.value) { case 'number': { const ret = { type: 'number', enum: [ty.value], }; return addMetaInfo(ret, ty); } case 'bigint': { const ret = { type: ['integer', 'string'], enum: [ty.value.toString()], }; if (BigInt(Number.MIN_SAFE_INTEGER) <= ty.value && ty.value <= BigInt(Number.MAX_SAFE_INTEGER)) { ret.enum.push(Number(ty.value)); } return addMetaInfo(ret, ty); } case 'string': { const ret = { type: 'string', enum: [ty.value], }; return addMetaInfo(ret, ty); } case 'boolean': { const ret = { type: 'boolean', enum: [ty.value], }; return addMetaInfo(ret, ty); } default: throw new Error(`Unknown primitive-value assertion: ${typeof ty.value}`); } } case 'never': { const ret = { type: 'null', }; return addMetaInfo(ret, ty); } case 'any': case 'unknown': { const ret = { type: ['null', 'integer', 'number', 'string', 'boolean', 'array', 'object'], }; return addMetaInfo(ret, ty); } case 'operator': throw new Error(`Unexpected type assertion: ${ty.kind}`); default: throw new Error(`Unknown type assertion: ${ty.kind}`); } } function generateJsonSchemaObject(schema) { const ret = { $schema: 'http://json-schema.org/draft-06/schema#', definitions: {}, }; for (const ty of schema.entries()) { if (ty[1].ty.kind === 'never' && ty[1].ty.passThruCodeBlock) { continue; } ret.definitions[ty[0]] = generateJsonSchemaInner(schema, ty[1].ty, 0); } return ret; } exports.generateJsonSchemaObject = generateJsonSchemaObject; function generateJsonSchema(schema, asTs) { const ret = generateJsonSchemaObject(schema); if (asTs) { return (`\n// tslint:disable: object-literal-key-quotes\n` + `const schema = ${JSON.stringify(ret, null, 2)};\nexport default schema;` + `\n// tslint:enable: object-literal-key-quotes\n`); } else { return JSON.stringify(ret, null, 2); } } exports.generateJsonSchema = generateJsonSchema; //# sourceMappingURL=json-schema.js.map