Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable JSON Schema multi file output #2526

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 69 additions & 12 deletions packages/quicktype-core/src/language/JSONSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,28 @@ import {
firstUpperWordStyle,
allUpperWordStyle
} from "../support/Strings";
import { defined, panic } from "../support/Support";
import { assert, defined, panic } from "../support/Support";
import { StringTypeMapping, getNoStringTypeMapping } from "../TypeBuilder";
import { addDescriptionToSchema } from "../attributes/Description";
import { Option } from "../RendererOptions";
import { BooleanOption, Option, OptionValues, getOptionValues } from "../RendererOptions";
import { RenderContext } from "../Renderer";
import { Sourcelike } from "Source";

export const jsonSchemaOptions = {
multiFileOutput: new BooleanOption(
"multi-file-output",
"Renders each top-level object in its own JSON schema file",
false
)
};

export class JSONSchemaTargetLanguage extends TargetLanguage {
constructor() {
super("JSON Schema", ["schema", "json-schema"], "schema");
}

protected getOptions(): Option<any>[] {
return [];
return [jsonSchemaOptions.multiFileOutput];
}

get stringTypeMapping(): StringTypeMapping {
Expand All @@ -41,9 +50,9 @@ export class JSONSchemaTargetLanguage extends TargetLanguage {

protected makeRenderer(
renderContext: RenderContext,
_untypedOptionValues: { [name: string]: any }
untypedOptionValues: { [name: string]: any }
): JSONSchemaRenderer {
return new JSONSchemaRenderer(this, renderContext);
return new JSONSchemaRenderer(this, renderContext, getOptionValues(jsonSchemaOptions, untypedOptionValues));
}
}

Expand All @@ -68,6 +77,16 @@ function jsonNameStyle(original: string): string {
type Schema = { [name: string]: any };

export class JSONSchemaRenderer extends ConvenienceRenderer {
private _currentFilename: string | undefined;

constructor(
targetLanguage: TargetLanguage,
renderContext: RenderContext,
private readonly _options: OptionValues<typeof jsonSchemaOptions>
) {
super(targetLanguage, renderContext);
}

protected makeNamedTypeNamer(): Namer {
return namingFunction;
}
Expand Down Expand Up @@ -199,25 +218,63 @@ export class JSONSchemaRenderer extends ConvenienceRenderer {
}

protected emitSourceStructure(): void {
// FIXME: Find a good way to do multiple top-levels. Maybe multiple files?
const topLevelType = this.topLevels.size === 1 ? this.schemaForType(defined(mapFirst(this.topLevels))) : {};
const schema = Object.assign({ $schema: "http://json-schema.org/draft-06/schema#" }, topLevelType);
const definitions: { [name: string]: Schema } = {};
this.forEachObject("none", (o: ObjectType, name: Name) => {
const title = defined(this.names.get(name));
definitions[title] = this.definitionForObject(o, title);
if (this._options.multiFileOutput === true)
this.outputFile(title, {
[title]: this.definitionForObject(o, title)
});
else definitions[title] = this.definitionForObject(o, title);
});
this.forEachUnion("none", (u, name) => {
if (!this.unionNeedsName(u)) return;
const title = defined(this.names.get(name));
definitions[title] = this.definitionForUnion(u, title);
if (this._options.multiFileOutput === true)
this.outputFile(title, {
[title]: this.definitionForUnion(u, title)
});
else definitions[title] = this.definitionForUnion(u, title);
});
this.forEachEnum("none", (e, name) => {
const title = defined(this.names.get(name));
definitions[title] = this.definitionForEnum(e, title);
if (this._options.multiFileOutput === true)
this.outputFile(title, {
[title]: this.definitionForEnum(e, title)
});
else definitions[title] = this.definitionForEnum(e, title);
});
schema.definitions = definitions;
if (this._options.multiFileOutput === false) this.outputFile("stdout", definitions);
}

outputFile(title: string, definitions: { [name: string]: Schema }) {
// FIXME: Find a good way to do multiple top-levels. Maybe multiple files?
const topLevelType = this.topLevels.size === 1 ? this.schemaForType(defined(mapFirst(this.topLevels))) : {};
const schema = Object.assign({ $schema: "http://json-schema.org/draft-06/schema#" }, topLevelType);
schema.definitions = definitions;
this.startFile(title);
this.emitMultiline(JSON.stringify(schema, undefined, " "));
this.endFile();
}

/// startFile takes a file name, lowercases it, appends ".schema" to it, and sets it as the current filename.
protected startFile(basename: Sourcelike): void {
if (this._options.multiFileOutput === false) {
return;
}

assert(this._currentFilename === undefined, "Previous file wasn't finished: " + this._currentFilename);
this._currentFilename = `${this.sourcelikeToString(basename)}.schema`;
this.initializeEmitContextForFilename(this._currentFilename);
}

/// endFile pushes the current file name onto the collection of finished files and then resets the current file name. These finished files are used in index.ts to write the output.
protected endFile(): void {
if (this._options.multiFileOutput === false) {
return;
}

this.finishFile(defined(this._currentFilename));
this._currentFilename = undefined;
}
}
Loading