Merge pull request #527 from quicktype/json-schema-roots

Add option to use all schema definitions as top-levels. Fixes #518
This commit is contained in:
Mark Probst 2018-02-14 06:46:09 -08:00 коммит произвёл GitHub
Родитель d2ede2aa52 bba2afb17e
Коммит d58ab2c563
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 55 добавлений и 23 удалений

Просмотреть файл

@ -11,7 +11,7 @@ import { unifyTypes } from "./UnifyClasses";
import { makeNamesTypeAttributes, modifyTypeNames, singularizeTypeNames } from "./TypeNames";
import { TypeAttributes, descriptionTypeAttributeKind, propertyDescriptionsTypeAttributeKind } from "./TypeAttributes";
enum PathElementKind {
export enum PathElementKind {
Root,
Definition,
OneOf,
@ -21,7 +21,7 @@ enum PathElementKind {
Items
}
type PathElement =
export type PathElement =
| { kind: PathElementKind.Root }
| { kind: PathElementKind.Definition; name: string }
| { kind: PathElementKind.OneOf; index: number }
@ -30,7 +30,7 @@ type PathElement =
| { kind: PathElementKind.AdditionalProperty }
| { kind: PathElementKind.Items };
type Ref = List<PathElement>;
export type Ref = List<PathElement>;
function checkStringArray(arr: any): string[] {
if (!Array.isArray(arr)) {
@ -127,12 +127,14 @@ function makeImmutablePath(path: Ref): List<any> {
return path.map(pe => fromJS(pe));
}
export function schemaToType(
export const rootRef: Ref = List([{ kind: PathElementKind.Root } as PathElement]);
export function addTypesInSchema(
typeBuilder: TypeGraphBuilder,
topLevelName: string,
rootJson: any,
conflateNumbers: boolean
): TypeRef {
conflateNumbers: boolean,
references: Map<string, Ref>
): void {
const root = checkStringMap(rootJson);
let typeForPath = Map<List<any>, TypeRef>();
@ -333,7 +335,18 @@ export function schemaToType(
return result;
}
const rootPathElement: PathElement = { kind: PathElementKind.Root };
const rootType = toType(root, List<PathElement>([rootPathElement]), makeNamesTypeAttributes(topLevelName, false));
return rootType;
references.forEach((topLevelRef, topLevelName) => {
const [target, targetPath] = lookupRef(root, rootRef, topLevelRef);
const t = toType(target, targetPath, makeNamesTypeAttributes(topLevelName, false));
typeBuilder.addTopLevel(topLevelName, t);
});
}
export function definitionRefsInSchema(rootJson: any): Map<string, Ref> {
if (typeof rootJson !== "object") return Map();
const definitions = rootJson.definitions;
if (typeof definitions !== "object") return Map();
return Map(Object.keys(definitions).map(name => {
return [name, rootRef.push({ kind: PathElementKind.Definition, name } as PathElement)] as [string, Ref];
}));
}

Просмотреть файл

@ -48,6 +48,7 @@ export interface CLIOptions {
graphqlSchema?: string;
graphqlIntrospect?: string;
graphqlServerHeader?: string[];
addSchemaTopLevel?: string;
template?: string;
out?: string;
buildMarkovChain?: string;
@ -88,7 +89,7 @@ function typeNameFromFilename(filename: string): string {
return name.substr(0, name.lastIndexOf("."));
}
async function samplesFromDirectory(dataDir: string): Promise<TypeSource[]> {
async function samplesFromDirectory(dataDir: string, schemaTopLevel: string | undefined): Promise<TypeSource[]> {
async function readFilesOrURLsInDirectory(d: string): Promise<TypeSource[]> {
const files = fs
.readdirSync(d)
@ -117,7 +118,8 @@ async function samplesFromDirectory(dataDir: string): Promise<TypeSource[]> {
} else if (file.endsWith(".schema")) {
sourcesInDir.push({
name,
schema: await readableFromFileOrUrl(fileOrUrl)
schema: await readableFromFileOrUrl(fileOrUrl),
topLevelRefs: schemaTopLevel === undefined ? undefined : [schemaTopLevel]
});
} else if (file.endsWith(".gqlschema")) {
assert(graphQLSchema === undefined, `More than one GraphQL schema in ${dataDir}`);
@ -247,6 +249,7 @@ function inferOptions(opts: Partial<CLIOptions>): CLIOptions {
graphqlSchema: opts.graphqlSchema,
graphqlIntrospect: opts.graphqlIntrospect,
graphqlServerHeader: opts.graphqlServerHeader,
addSchemaTopLevel: opts.addSchemaTopLevel,
template: opts.template
};
/* tslint:enable */
@ -301,6 +304,12 @@ const optionDefinitions: OptionDefinition[] = [
type: Boolean,
description: "Don't combine similar classes."
},
{
name: "add-schema-top-level",
type: String,
typeLabel: "REF",
description: "Use JSON Schema definitions as top-levels. Must be `definitions/`."
},
{
name: "graphql-schema",
type: String,
@ -461,7 +470,7 @@ function parseArgv(argv: string[]): CLIOptions {
// will throw if it encounters an unknown option.
function parseOptions(definitions: OptionDefinition[], argv: string[], partial: boolean): CLIOptions {
const opts: { [key: string]: any } = commandLineArgs(definitions, { argv, partial });
const options: { rendererOptions: RendererOptions; [key: string]: any } = { rendererOptions: {} };
const options: { rendererOptions: RendererOptions;[key: string]: any } = { rendererOptions: {} };
definitions.forEach(o => {
if (!(o.name in opts)) return;
const v = opts[o.name];
@ -516,7 +525,7 @@ async function getSources(options: CLIOptions): Promise<TypeSource[]> {
let sources: TypeSource[] = [];
for (const dataDir of directories) {
sources = sources.concat(await samplesFromDirectory(dataDir));
sources = sources.concat(await samplesFromDirectory(dataDir, options.addSchemaTopLevel));
}
// Every src that's not a directory is assumed to be a file or URL
@ -603,7 +612,8 @@ export async function main(args: string[] | Partial<CLIOptions>) {
assert(source.samples.length === 1, `Please specify one schema file for ${source.name}`);
sources.push({
name: source.name,
schema: source.samples[0]
schema: source.samples[0],
topLevelRefs: options.addSchemaTopLevel === undefined ? undefined : [options.addSchemaTopLevel]
});
} else {
sources.push(source);

Просмотреть файл

@ -6,10 +6,10 @@ import { Readable } from "stream";
import * as targetLanguages from "./Language/All";
import { TargetLanguage } from "./TargetLanguage";
import { SerializedRenderResult, Annotation, Location, Span } from "./Source";
import { assertNever } from "./Support";
import { assertNever, assert } from "./Support";
import { CompressedJSON, Value } from "./CompressedJSON";
import { combineClasses, findSimilarityCliques } from "./CombineClasses";
import { schemaToType } from "./JSONSchemaInput";
import { addTypesInSchema, definitionRefsInSchema, Ref, rootRef } from "./JSONSchemaInput";
import { TypeInference } from "./Inference";
import { inferMaps } from "./InferMaps";
import { TypeGraphBuilder } from "./TypeBuilder";
@ -54,6 +54,7 @@ export function isJSONSource(source: TypeSource): source is JSONTypeSource {
export interface SchemaTypeSource {
name: string;
schema: StringInput;
topLevelRefs?: string[];
}
export function isSchemaSource(source: TypeSource): source is SchemaTypeSource {
@ -108,7 +109,7 @@ const defaultOptions: Options = {
type InputData = {
samples: { [name: string]: Value[] };
schemas: { [name: string]: any };
schemas: { [name: string]: { schema: any; topLevelRefs: string[] | undefined } };
graphQLs: { [name: string]: { schema: any; query: string } };
};
@ -149,12 +150,20 @@ export class Run {
if (this._options.findSimilarClassesSchema !== undefined) {
const schema = JSON.parse(this._options.findSimilarClassesSchema);
const name = "ComparisonBaseRoot";
typeBuilder.addTopLevel(name, schemaToType(typeBuilder, name, schema, conflateNumbers));
addTypesInSchema(typeBuilder, schema, conflateNumbers, Map([[name, rootRef] as [string, Ref]]));
}
// JSON Schema
Map(this._allInputs.schemas).forEach((schema, name) => {
typeBuilder.addTopLevel(name, schemaToType(typeBuilder, name, schema, conflateNumbers));
Map(this._allInputs.schemas).forEach(({ schema, topLevelRefs }, name) => {
let references: Map<string, Ref>;
if (topLevelRefs === undefined) {
references = Map([[name, rootRef] as [string, Ref]]);
} else {
assert(topLevelRefs.length === 1 && topLevelRefs[0] === "definitions/", "Schema top level refs must be `definitions/`");
references = definitionRefsInSchema(schema);
assert(references.size > 0, "No definitions in JSON Schema");
}
addTypesInSchema(typeBuilder, schema, conflateNumbers, references);
});
// GraphQL
@ -238,12 +247,12 @@ export class Run {
this._allInputs.samples[name].push(input);
}
} else if (isSchemaSource(source)) {
const { name, schema } = source;
const { name, schema, topLevelRefs } = source;
const input = JSON.parse(await toString(schema));
if (_.has(this._allInputs.schemas, name)) {
throw new Error(`More than one schema given for ${name}`);
}
this._allInputs.schemas[name] = input;
this._allInputs.schemas[name] = { schema: input, topLevelRefs };
} else {
assertNever(source);
}