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:
Коммит
d58ab2c563
|
@ -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];
|
||||
}));
|
||||
}
|
||||
|
|
20
src/cli.ts
20
src/cli.ts
|
@ -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);
|
||||
|
|
25
src/index.ts
25
src/index.ts
|
@ -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);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче