Skip to content
Open
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
28 changes: 28 additions & 0 deletions packages/quicktype-core/src/attributes/AccessorNames.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
areEqual,
iterableFirst,
mapFromIterable,
mapFromObject,
Expand Down Expand Up @@ -28,11 +29,38 @@ export type AccessorEntry = string | Map<string, string>;

export type AccessorNames = Map<string, AccessorEntry>;

// Combines the accessor names of types that turn out to be the same type,
// such as an enum and the same enum in a union, or two enums with the same
// cases: an entry they all agree on is kept; one they disagree on is
// dropped, so that name falls back to the default.
function combineAccessorNames(arr: AccessorNames[]): AccessorNames {
const result: AccessorNames = new Map();
const conflicting = new Set<string>();
for (const accessors of arr) {
for (const [key, entry] of accessors) {
if (conflicting.has(key)) continue;
const existing = result.get(key);
if (existing === undefined) {
result.set(key, entry);
} else if (!areEqual(existing, entry)) {
result.delete(key);
conflicting.add(key);
}
}
}

return result;
}

class AccessorNamesTypeAttributeKind extends TypeAttributeKind<AccessorNames> {
public constructor() {
super("accessorNames");
}

public combine(arr: AccessorNames[]): AccessorNames {
return combineAccessorNames(arr);
}

public makeInferred(_: AccessorNames): undefined {
return undefined;
}
Expand Down
10 changes: 9 additions & 1 deletion packages/quicktype-core/src/attributes/EnumValues.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mapMap } from "collection-utils";
import { areEqual, mapMap } from "collection-utils";

import type {
JSONSchemaAttributes,
Expand All @@ -20,6 +20,14 @@ class EnumValuesTypeAttributeKind extends TypeAttributeKind<AccessorNames> {
super("enumValues");
}

// Copies of an enum's values that are all the same are kept; any other
// combination still fails, because dropping a value the copies disagree
// on could give two cases the same value.
public combine(arr: AccessorNames[]): AccessorNames | undefined {
if (arr.every((values) => areEqual(values, arr[0]))) return arr[0];
return super.combine(arr);
}

public makeInferred(_: AccessorNames): undefined {
return undefined;
}
Expand Down
4 changes: 4 additions & 0 deletions test/inputs/schema/accessors-enum-union.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"f": "a",
"g": "b"
}
4 changes: 4 additions & 0 deletions test/inputs/schema/accessors-enum-union.2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"f": null,
"g": "a"
}
4 changes: 4 additions & 0 deletions test/inputs/schema/accessors-enum-union.3.fail.enum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"f": "c",
"g": "a"
}
21 changes: 21 additions & 0 deletions test/inputs/schema/accessors-enum-union.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"type": "object",
"properties": {
"f": {
"anyOf": [
{ "type": "string", "enum": ["a", "b"] },
{ "type": "null" }
]
},
"g": { "$ref": "#/definitions/E" }
},
"required": ["f", "g"],
"additionalProperties": false,
"definitions": {
"E": {
"type": "string",
"enum": ["a", "b"],
"qt-accessors": { "a": "Alpha", "b": "Beta" }
}
}
}
48 changes: 48 additions & 0 deletions test/unit/enum-values-combine.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { InputData, JSONSchemaInput, quicktype } from "quicktype-core";
import { describe, expect, test } from "vitest";

// Two definitions of the same enum are one type, so their `qt-enum-values`
// are combined.
async function cJSONOutput(fValues: Record<string, string>): Promise<string> {
const schema = JSON.stringify({
definitions: {
E: {
type: "string",
enum: ["a", "b"],
"qt-enum-values": { a: "0", b: "1" },
},
F: {
type: "string",
enum: ["a", "b"],
"qt-enum-values": fValues,
},
},
});
const schemaInput = new JSONSchemaInput(undefined);
for (const name of ["E", "F"]) {
await schemaInput.addSource({
name,
schema,
uris: [`#/definitions/${name}`],
});
}
const inputData = new InputData();
inputData.addInput(schemaInput);

const result = await quicktype({ inputData, lang: "cjson" });
return result.lines.join("\n");
}

describe("enum values of the same enum", () => {
test("combine when every copy is the same", async () => {
const output = await cJSONOutput({ b: "1", a: "0" });
expect(output).toContain("_A = 0,");
expect(output).toContain("_B = 1,");
});

test("still fail when the copies differ", async () => {
await expect(cJSONOutput({ a: "0", b: "2" })).rejects.toThrow(
"Cannot combine type attribute enumValues",
);
});
});