Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions packages/quicktype-core/src/Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export type ErrorProperties =
properties: { dir: string };
}
| { kind: "DriverCannotMixNonJSONInputs"; properties: { dir: string } }
| { kind: "DriverNoSamplesForTopLevel"; properties: { name: string } }
| { kind: "DriverUnknownDebugOption"; properties: { option: string } }
| { kind: "DriverNoLanguageOrExtension"; properties: Record<string, never> }
| { kind: "DriverCLIOptionParsingFailed"; properties: { message: string } }
Expand Down Expand Up @@ -239,6 +240,7 @@ const errorMessages: ErrorMessages = {
"Cannot mix JSON samples with JSON Schems, GraphQL, or TypeScript in input subdirectory ${dir}",
DriverCannotMixNonJSONInputs:
"Cannot mix JSON Schema, GraphQL, and TypeScript in an input subdirectory ${dir}",
DriverNoSamplesForTopLevel: "No JSON samples given for top-level ${name}",
DriverUnknownDebugOption: "Unknown debug option ${option}",
DriverNoLanguageOrExtension:
"Please specify a language (--lang) or an output file extension",
Expand Down
8 changes: 8 additions & 0 deletions packages/quicktype-core/src/input/Inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export class JSONInput<T> implements Input<JSONSourceData<T>> {

public async addSource(source: JSONSourceData<T>): Promise<void> {
const { name, samples, description } = source;
if (samples.length === 0) {
return messageError("DriverNoSamplesForTopLevel", { name });
}

try {
const values = await arrayMapSync(
samples,
Expand All @@ -133,6 +137,10 @@ export class JSONInput<T> implements Input<JSONSourceData<T>> {

public addSourceSync(source: JSONSourceData<T>): void {
const { name, samples, description } = source;
if (samples.length === 0) {
return messageError("DriverNoSamplesForTopLevel", { name });
}

try {
const values = samples.map((s) =>
this._compressedJSON.parseSync(s),
Expand Down
44 changes: 44 additions & 0 deletions test/unit/empty-json-samples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// addSource with an empty samples array used to succeed silently and
// quicktype would render full converter boilerplate for a type with no
// evidence whatsoever. An empty samples array is almost always a caller bug
// (a glob that matched nothing, a filtered list that came up empty), so it
// must fail loudly instead — see
// https://github.com/glideapps/quicktype/issues/2934.

import { describe, expect, test } from "vitest";

import {
InputData,
jsonInputForTargetLanguage,
quicktype,
} from "../../packages/quicktype-core/src/index.js";

describe("JSON input with empty samples", () => {
test("addSource throws a clear error", async () => {
const jsonInput = jsonInputForTargetLanguage("typescript");
await expect(
jsonInput.addSource({ name: "X", samples: [] }),
).rejects.toThrow("No JSON samples given for top-level X");
});

test("addSourceSync throws a clear error", () => {
const jsonInput = jsonInputForTargetLanguage("typescript");
expect(() =>
jsonInput.addSourceSync({ name: "X", samples: [] }),
).toThrow("No JSON samples given for top-level X");
});

test("non-empty samples still generate code", async () => {
const jsonInput = jsonInputForTargetLanguage("typescript");
await jsonInput.addSource({
name: "Person",
samples: ['{"name":"Alice","age":30}'],
});

const inputData = new InputData();
inputData.addInput(jsonInput);
const result = await quicktype({ inputData, lang: "typescript" });

expect(result.lines.join("\n")).toContain("Person");
});
});
Loading