tynder
Version:
TypeScript friendly Data validator for JavaScript.
331 lines • 12.4 kB
JavaScript
;
// 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