From 7fefd37b363d30dbb33a2c471f318c93e45a83c2 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Tue, 17 Jul 2018 18:50:43 +0200 Subject: [PATCH] Allow adding additional schemas for $id. Fixes #967 --- src/cli/index.ts | 24 ++++++++++++++++++--- src/quicktype-core/Messages.ts | 6 ++++-- src/quicktype-core/input/Inputs.ts | 16 +++++++++----- src/quicktype-core/input/JSONSchemaInput.ts | 11 +++++++++- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index 0d91131b..0a0d4346 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -56,6 +56,7 @@ export interface CLIOptions { src: string[]; srcUrls?: string; srcLang: string; + additionalSchema: string[]; graphqlSchema?: string; graphqlIntrospect?: string; httpHeader?: string[]; @@ -275,6 +276,7 @@ function inferCLIOptions(opts: Partial, targetLanguage: TargetLangua version: opts.version || false, out: opts.out, buildMarkovChain: opts.buildMarkovChain, + additionalSchema: opts.additionalSchema || [], graphqlSchema: opts.graphqlSchema, graphqlIntrospect: opts.graphqlIntrospect, httpMethod: opts.httpMethod, @@ -397,6 +399,14 @@ function makeOptionDefinitions(targetLanguages: TargetLanguage[]): OptionDefinit typeLabel: "HEADER", description: "HTTP header for the GraphQL introspection query." }, + { + name: "additional-schema", + alias: "S", + type: String, + multiple: true, + typeLabel: "FILE", + description: "Register the $id's of additional JSON Schema files." + }, { name: "no-render", type: Boolean, @@ -644,7 +654,11 @@ function makeTypeScriptSource(fileNames: string[]): SchemaTypeSource { return Object.assign({ kind: "schema" }, schemaForTypeScriptSources(sources)) as SchemaTypeSource; } -async function makeInputData(sources: TypeSource[], targetLanguage: TargetLanguage): Promise { +async function makeInputData( + sources: TypeSource[], + targetLanguage: TargetLanguage, + additionalSchemaAddresses: ReadonlyArray +): Promise { const inputData = new InputData(); for (const source of sources) { @@ -656,7 +670,11 @@ async function makeInputData(sources: TypeSource[], targetLanguage: TargetLangua await inputData.addSource("json", source, () => jsonInputForTargetLanguage(targetLanguage)); break; case "schema": - await inputData.addSource("schema", source, () => new JSONSchemaInput(new FetchingJSONSchemaStore())); + await inputData.addSource( + "schema", + source, + () => new JSONSchemaInput(new FetchingJSONSchemaStore(), [], additionalSchemaAddresses) + ); break; default: return assertNever(source); @@ -800,7 +818,7 @@ export async function makeQuicktypeOptions( return messageError("DriverUnknownOutputLanguage", { lang: options.lang }); } - const inputData = await makeInputData(sources, lang); + const inputData = await makeInputData(sources, lang, options.additionalSchema); const quicktypeOptions: Partial = { lang, diff --git a/src/quicktype-core/Messages.ts b/src/quicktype-core/Messages.ts index facd7dd5..f6e1df81 100644 --- a/src/quicktype-core/Messages.ts +++ b/src/quicktype-core/Messages.ts @@ -39,6 +39,7 @@ export type ErrorProperties = | { kind: "SchemaKeyNotInObject"; properties: { key: string; ref: Ref } } | { kind: "SchemaFetchError"; properties: { address: string; base: Ref } } | { kind: "SchemaFetchErrorTopLevel"; properties: { address: string } } + | { kind: "SchemaFetchErrorAdditional"; properties: { address: string } } // GraphQL input | { kind: "GraphQLNoQueriesDefined"; properties: {} } @@ -110,8 +111,9 @@ const errorMessages: ErrorMessages = { "Trying to index array in schema with key that is not a number, but is ${actual} at ${ref}", SchemaIndexNotInArray: "Index ${index} out of range of schema array at ${ref}", SchemaKeyNotInObject: "Key ${key} not in schema object at ${ref}", - SchemaFetchError: "Could not fetch schema ${address}, referred to from ${base}: ${error}", - SchemaFetchErrorTopLevel: "Could not fetch top-level schema ${address}: ${error}", + SchemaFetchError: "Could not fetch schema ${address}, referred to from ${base}", + SchemaFetchErrorTopLevel: "Could not fetch top-level schema ${address}", + SchemaFetchErrorAdditional: "Could not fetch additional schema ${address}", // GraphQL input GraphQLNoQueriesDefined: "GraphQL file doesn't have any queries defined.", diff --git a/src/quicktype-core/input/Inputs.ts b/src/quicktype-core/input/Inputs.ts index 5633b517..bc246816 100644 --- a/src/quicktype-core/input/Inputs.ts +++ b/src/quicktype-core/input/Inputs.ts @@ -161,7 +161,6 @@ export class JSONSchemaInput implements Input { readonly kind: string = "schema"; readonly needSchemaProcessing: boolean = true; - private _schemaStore: JSONSchemaStore | undefined = undefined; private readonly _attributeProducers: JSONSchemaAttributeProducer[]; private readonly _schemaInputs: Map = new Map(); @@ -172,10 +171,10 @@ export class JSONSchemaInput implements Input { private _needIR: boolean = false; constructor( - givenSchemaStore: JSONSchemaStore | undefined, - additionalAttributeProducers: JSONSchemaAttributeProducer[] = [] + private _schemaStore: JSONSchemaStore | undefined, + additionalAttributeProducers: JSONSchemaAttributeProducer[] = [], + private readonly _additionalSchemaAddresses: ReadonlyArray = [] ) { - this._schemaStore = givenSchemaStore; this._attributeProducers = [descriptionAttributeProducer, accessorNamesAttributeProducer].concat( additionalAttributeProducers ); @@ -190,7 +189,14 @@ export class JSONSchemaInput implements Input { } async addTypes(ctx: RunContext, typeBuilder: TypeBuilder): Promise { - await addTypesInSchema(ctx, typeBuilder, defined(this._schemaStore), this._topLevels, this._attributeProducers); + await addTypesInSchema( + ctx, + typeBuilder, + defined(this._schemaStore), + this._topLevels, + this._attributeProducers, + this._additionalSchemaAddresses + ); } async addSource(schemaSource: JSONSchemaSourceData): Promise { diff --git a/src/quicktype-core/input/JSONSchemaInput.ts b/src/quicktype-core/input/JSONSchemaInput.ts index 9c03b358..5bf84e36 100644 --- a/src/quicktype-core/input/JSONSchemaInput.ts +++ b/src/quicktype-core/input/JSONSchemaInput.ts @@ -493,7 +493,8 @@ export async function addTypesInSchema( typeBuilder: TypeBuilder, store: JSONSchemaStore, references: ReadonlyMap, - attributeProducers: JSONSchemaAttributeProducer[] + attributeProducers: JSONSchemaAttributeProducer[], + additionalSchemaAddresses: ReadonlyArray ): Promise { const canonizer = new Canonizer(ctx); @@ -894,6 +895,14 @@ export async function addTypesInSchema( return result; } + for (const address of additionalSchemaAddresses) { + const schema = await store.get(address, ctx.debugPrintSchemaResolving); + if (schema === undefined) { + return messageError("SchemaFetchErrorAdditional", { address }); + } + canonizer.addSchema(schema, address); + } + for (const [topLevelName, topLevelRef] of references) { const [target, loc] = await resolveVirtualRef( new Location(new Ref(topLevelRef.addressURI, [])),