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
7 changes: 7 additions & 0 deletions packages/quicktype-core/src/language/Rust/RustRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ export class RustRenderer extends ConvenienceRenderer {
const rustType = this.rustType(t, withIssues);
const isCycleBreaker = this.isCycleBreakerType(t);

if (isCycleBreaker && t instanceof UnionType) {
const nullable = nullableFromUnion(t);
if (nullable === null || this.isCycleBreakerType(nullable)) {
return rustType;
}
}

return isCycleBreaker ? ["Box<", rustType, ">"] : rustType;
}

Expand Down
4 changes: 4 additions & 0 deletions test/inputs/schema/rust-cycle-breaker-union.1.fail.union.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"value": "root",
"next": 42
}
7 changes: 7 additions & 0 deletions test/inputs/schema/rust-cycle-breaker-union.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"value": "root",
"next": {
"value": "leaf",
"next": "done"
}
}
16 changes: 16 additions & 0 deletions test/inputs/schema/rust-cycle-breaker-union.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Node",
"type": "object",
"properties": {
"value": { "type": "string" },
"next": {
"anyOf": [
{ "$ref": "#" },
{ "type": "string" },
{ "type": "null" }
]
}
},
"required": ["value"]
}
7 changes: 7 additions & 0 deletions test/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,11 @@ export const CJSONLanguage: Language = {
"direct-union.schema",
"optional-any.schema",
"recursive-union-flattening.schema",
/* Self-referential union member (a union whose member recursively
* refers back to the enclosing object) is not supported by the
* multi-source renderer; generation aborts. Pre-existing cJSON
* limitation, unrelated to the Rust fixture this schema targets. */
"rust-cycle-breaker-union.schema",
"required-non-properties.schema",
/* Class elements with invalid type are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */
...skipsUntypedUnions,
Expand Down Expand Up @@ -887,6 +892,7 @@ export const ElmLanguage: Language = {
"vega-lite.schema", // recursion
"simple-ref.schema", // recursion
"recursive-union-flattening.schema", // recursion
"rust-cycle-breaker-union.schema", // recursion
// elm/json's field decoder uses the JS `in` operator, which finds
// inherited Object.prototype members, so an absent "constructor"
// property decodes to the object's constructor function.
Expand Down Expand Up @@ -1650,6 +1656,7 @@ export const KotlinXLanguage: Language = {
"mutually-recursive.schema",
"prefix-items.schema",
"recursive-union-flattening.schema",
"rust-cycle-breaker-union.schema",
"tuple.schema",
"union-int-double.schema",
"union-list.schema",
Expand Down
4 changes: 2 additions & 2 deletions test/unit/cjson-enum-default.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ describe("cJSON enum invalid value", () => {
const output = await cJSONOutput();

expect(output).toContain(`enum Subscription {
SUBSCRIPTION_CONFIG = 1,
SUBSCRIPTION_STATE = 1,
SUBSCRIPTION_CONFIG,
SUBSCRIPTION_HEARTBEAT,
SUBSCRIPTION_STATE,
};`);
expect(output).toContain("enum Subscription x = 0;");
});
Expand Down
48 changes: 48 additions & 0 deletions test/unit/rust-cycle-breaker-boxing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
InputData,
JSONSchemaInput,
quicktype,
} from "../../packages/quicktype-core/src/index.js";
import { describe, expect, test } from "vitest";

async function renderRust(next: object, requireNext = false): Promise<string> {
const schemaInput = new JSONSchemaInput(undefined);
await schemaInput.addSource({
name: "Node",
schema: JSON.stringify({
$schema: "http://json-schema.org/draft-07/schema#",
title: "Node",
type: "object",
properties: {
value: { type: "string" },
next,
},
required: requireNext ? ["value", "next"] : ["value"],
}),
});

const inputData = new InputData();
inputData.addInput(schemaInput);

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

const recursiveUnionMembers = [{ $ref: "#" }, { type: "string" }];

describe("Rust cycle-breaker boxing", () => {
test("does not box an Option whose union member is already boxed", async () => {
const output = await renderRust({
anyOf: [...recursiveUnionMembers, { type: "null" }],
});

expect(output).toContain("pub next: Option<Box<Next>>,");
expect(output).not.toContain("Box<Option<Box<Next>>>");
});

test("keeps a non-nullable cycle-breaking union boxed", async () => {
const output = await renderRust({ anyOf: recursiveUnionMembers }, true);

expect(output).toContain("pub next: Box<Next>,");
});
});
Loading