Implement the jsonSchema generator
parent
d481368563
commit
df240ba87f
|
@ -28,7 +28,11 @@ export default class Fund {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
flatten(): Type[] {
|
takeAll(): Iterable<Type> {
|
||||||
return Array.from(this._types.values());
|
return this._types.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
takeTops(): Iterable<Type> {
|
||||||
|
return this._tops;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
import wu from 'wu';
|
||||||
|
|
||||||
|
import {invariant, collect} from '../utils';
|
||||||
|
import type Fund from '../fund';
|
||||||
|
import type {Type, NumberType} from '../types';
|
||||||
|
|
||||||
|
export type SchemaType = 'object' | 'array' | 'boolean' | 'integer' | 'number' | 'string' | 'null';
|
||||||
|
|
||||||
|
export type Schema = {
|
||||||
|
id?: string,
|
||||||
|
$ref?: string,
|
||||||
|
$schema?: string,
|
||||||
|
title?: string,
|
||||||
|
description?: string,
|
||||||
|
default?: mixed,
|
||||||
|
multipleOf?: number,
|
||||||
|
maximum?: number,
|
||||||
|
exclusiveMaximum?: boolean,
|
||||||
|
minimum?: number,
|
||||||
|
exclusiveMinimum?: boolean,
|
||||||
|
maxLength?: number,
|
||||||
|
minLength?: number,
|
||||||
|
pattern?: string,
|
||||||
|
additionalItems?: boolean | Schema,
|
||||||
|
items?: Schema | Schema[],
|
||||||
|
maxItems?: number,
|
||||||
|
minItems?: number,
|
||||||
|
uniqueItems?: boolean,
|
||||||
|
maxProperties?: number,
|
||||||
|
minProperties?: number,
|
||||||
|
required?: string[],
|
||||||
|
additionalProperties?: boolean | Schema,
|
||||||
|
definitions?: {[string]: Schema},
|
||||||
|
properties?: {[string]: Schema},
|
||||||
|
patternProperties?: {[string]: Schema},
|
||||||
|
dependencies?: {[string]: Schema | string[]},
|
||||||
|
enum?: mixed[],
|
||||||
|
type?: SchemaType | SchemaType[],
|
||||||
|
allOf?: Schema[],
|
||||||
|
anyOf?: Schema[],
|
||||||
|
oneOf?: Schema[],
|
||||||
|
not?: Schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
function convert(fund: Fund, type: ?Type): Schema {
|
||||||
|
if (!type) {
|
||||||
|
return {
|
||||||
|
type: 'null',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type.kind) {
|
||||||
|
case 'record':
|
||||||
|
const properties = collect(
|
||||||
|
wu(type.fields).map(field => [field.name, convert(fund, field.value)])
|
||||||
|
);
|
||||||
|
|
||||||
|
const required = wu(type.fields)
|
||||||
|
.filter(field => field.required)
|
||||||
|
.pluck('name')
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const schema: Schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (required.length > 0) {
|
||||||
|
schema.required = required;
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
case 'array':
|
||||||
|
return {
|
||||||
|
type: 'array',
|
||||||
|
items: convert(fund, type.items),
|
||||||
|
};
|
||||||
|
case 'tuple':
|
||||||
|
return {
|
||||||
|
type: 'array',
|
||||||
|
items: wu(type.items).map(type => convert(fund, type)).toArray(),
|
||||||
|
};
|
||||||
|
case 'map':
|
||||||
|
// TODO: invariant(type.keys.kind === 'string');
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: convert(fund, type.values),
|
||||||
|
};
|
||||||
|
case 'union':
|
||||||
|
const enumerate = wu(type.variants)
|
||||||
|
.filter(variant => variant.kind === 'literal')
|
||||||
|
.map(literal => (literal: $FlowFixMe).value)
|
||||||
|
.tap(value => invariant(value !== undefined))
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const schemas = wu(type.variants)
|
||||||
|
.filter(variant => variant.kind !== 'literal')
|
||||||
|
.map(variant => convert(fund, variant))
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
if (schemas.length === 0) {
|
||||||
|
return {
|
||||||
|
enum: enumerate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumerate.length > 0) {
|
||||||
|
schemas.push({
|
||||||
|
enum: enumerate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
anyOf: schemas,
|
||||||
|
};
|
||||||
|
case 'intersection':
|
||||||
|
return {
|
||||||
|
allOf: wu(type.parts).map(part => convert(fund, part)).toArray(),
|
||||||
|
};
|
||||||
|
case 'maybe':
|
||||||
|
return {
|
||||||
|
oneOf: [convert(fund, type.value), {type: 'null'}],
|
||||||
|
};
|
||||||
|
case 'number':
|
||||||
|
return {
|
||||||
|
type: type.repr === 'f32' || type.repr === 'f64' ? 'number' : 'integer',
|
||||||
|
};
|
||||||
|
case 'string':
|
||||||
|
return {
|
||||||
|
type: 'string',
|
||||||
|
};
|
||||||
|
case 'boolean':
|
||||||
|
return {
|
||||||
|
type: 'boolean',
|
||||||
|
};
|
||||||
|
case 'literal':
|
||||||
|
invariant(type.value !== undefined);
|
||||||
|
|
||||||
|
return type.value === null ? {
|
||||||
|
type: 'null',
|
||||||
|
} : {
|
||||||
|
enum: [type.value],
|
||||||
|
};
|
||||||
|
case 'any':
|
||||||
|
case 'mixed':
|
||||||
|
return {};
|
||||||
|
case 'reference':
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
$ref: `#/definitions/${type.to.join('::')}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (fund: Fund): Schema {
|
||||||
|
const schemas = wu(fund.takeAll()).map(type => {
|
||||||
|
invariant(type.id);
|
||||||
|
|
||||||
|
return [type.id.join('::'), convert(fund, type)];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
definitions: collect(schemas),
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
import Parser from './parser';
|
import Parser from './parser';
|
||||||
import Collector from './collector';
|
import Collector from './collector';
|
||||||
import type {Type} from './types';
|
import type {Type} from './types';
|
||||||
|
import generateJsonSchema from './generators/jsonSchema';
|
||||||
|
import type {Schema} from './generators/jsonSchema';
|
||||||
|
|
||||||
// @see babel#6805.
|
// @see babel#6805.
|
||||||
//export {Parser, Collector};
|
//export {Parser, Collector};
|
||||||
|
|
||||||
export default function (path: string): {+types: Type[]} {
|
export default function (path: string): {+types: Type[], +schema: Schema} {
|
||||||
const parser = new Parser;
|
const parser = new Parser;
|
||||||
const collector = new Collector(parser);
|
const collector = new Collector(parser);
|
||||||
|
|
||||||
|
@ -14,6 +16,7 @@ export default function (path: string): {+types: Type[]} {
|
||||||
const fund = collector.finish();
|
const fund = collector.finish();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
types: fund.flatten(),
|
types: Array.from(fund.takeAll()),
|
||||||
|
schema: generateJsonSchema(fund),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
10
src/utils.js
10
src/utils.js
|
@ -9,3 +9,13 @@ export function last<T>(list: T[]): T {
|
||||||
|
|
||||||
return list[list.length - 1];
|
return list[list.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function collect<T>(iter: Iterable<[string, T]>): {[string]: T} {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const [key, value] of iter) {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue