Skip to content

Commit 9cb1bd3

Browse files
authored
Merge pull request #393 from json-schema-tools/fix/cycle-boxing-rust
feat: augment schemas with isCycle
2 parents feb860c + 75b3245 commit 9cb1bd3

File tree

11 files changed

+370
-311
lines changed

11 files changed

+370
-311
lines changed

package-lock.json

Lines changed: 233 additions & 282 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@
4040
"@typescript-eslint/eslint-plugin": "^4.6.0",
4141
"@typescript-eslint/parser": "^4.6.0",
4242
"eslint": "^7.12.1",
43-
"inquirer": "^8.1.0",
43+
"inquirer": "^8.1.1",
4444
"jest": "^26.6.1",
4545
"node-fetch": "^2.6.1",
4646
"ts-jest": "^26.4.3",
47-
"typedoc": "^0.20.36",
47+
"typedoc": "^0.20.37",
4848
"typescript": "4.2.4"
4949
},
5050
"dependencies": {

src/codegens/rust.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,8 @@ export default class Rust extends CodeGen {
144144
isRequired = s.required.indexOf(key) !== -1;
145145
}
146146

147-
const refTitle = this.refToTitle(propSchema);
148-
let typeName = this.getSafeTitle(refTitle);
149-
const isCycle = refTitle === s.title;
150-
if (isCycle) {
147+
let typeName = this.getSafeTitle(this.refToTitle(propSchema));
148+
if (propSchema !== false && propSchema !== true && propSchema.isCycle) {
151149
typeName = `Box<${typeName}>`;
152150
}
153151

@@ -215,8 +213,7 @@ export default class Rust extends CodeGen {
215213

216214
protected handleUntypedObject(s: JSONSchemaObject): TypeIntermediateRepresentation {
217215
if (s.additionalProperties) {
218-
const refTitle = this.refToTitle(s.additionalProperties);
219-
const typeName = this.getSafeTitle(refTitle);
216+
const typeName = this.getSafeTitle(this.refToTitle(s.additionalProperties));
220217
const propertyTypings = [
221218
"#[serde(flatten)]",
222219
`pub additional_properties: Option<${typeName}>`
@@ -273,20 +270,25 @@ export default class Rust extends CodeGen {
273270
}
274271

275272
private buildEnum(s: JSONSchema[]): TypeIntermediateRepresentation {
273+
const typeLines = s
274+
.map((enumItem) => {
275+
const typeName = this.getSafeTitle(this.refToTitle(enumItem));
276+
let rhsTypeName = typeName;
277+
if (enumItem !== false && enumItem !== true && enumItem.isCycle) {
278+
rhsTypeName = `Box<${typeName}>`;
279+
}
280+
return `${typeName}(${rhsTypeName})`;
281+
})
282+
.map((l) => ` ${l},`)
283+
.join("\n");
284+
276285
return {
277286
macros: [
278287
"#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]",
279288
"#[serde(untagged)]"
280289
].join("\n"),
281290
prefix: "enum",
282-
typing: [
283-
"{",
284-
this.getJoinedSafeTitles(s, "\n")
285-
.split("\n")
286-
.map((l) => ` ${l}(${l}),`)
287-
.join("\n"),
288-
"}",
289-
].join("\n"),
291+
typing: ["{", typeLines, "}"].join("\n"),
290292
imports: [
291293
"use serde::{Serialize, Deserialize};",
292294
]

src/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { JSONSchema } from "@json-schema-tools/meta-schema";
2-
import { capitalize, combineSchemas, replaceTypeAsArrayWithOneOf } from "./utils";
2+
import { capitalize, combineSchemas, replaceTypeAsArrayWithOneOf, getCycleMap, setIsCycle } from "./utils";
33
import titleizer from "@json-schema-tools/titleizer";
44
import referencer from "@json-schema-tools/referencer";
55
import { CodeGen } from "./codegens/codegen";
@@ -21,11 +21,13 @@ export class Transpiler {
2121
const inputSchema: JSONSchema[] = useMerge ? s as JSONSchema[] : [s];
2222
const noTypeArrays = inputSchema.map(replaceTypeAsArrayWithOneOf);
2323
const schemaWithTitles = noTypeArrays.map(titleizer);
24+
const cycleMap = getCycleMap(schemaWithTitles);
2425
const reffed = schemaWithTitles.map(referencer);
26+
const reffedAndCycleMarked = reffed.map((s) => setIsCycle(s, cycleMap));
2527
if (useMerge) {
26-
this.megaSchema = combineSchemas(reffed);
28+
this.megaSchema = combineSchemas(reffedAndCycleMarked);
2729
} else {
28-
[this.megaSchema] = reffed;
30+
[this.megaSchema] = reffedAndCycleMarked;
2931
}
3032
}
3133

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,37 @@
1+
import "encoding/json"
2+
import "errors"
3+
type StringDoaGddGA string
4+
type OneOfMoebiusSchemaStringDoaGddGANK2MA6NR struct {
5+
MoebiusSchema *MoebiusSchema
6+
StringDoaGddGA *StringDoaGddGA
7+
}
8+
// UnmarshalJSON implements the json Unmarshaler interface.
9+
// This implementation DOES NOT assert that ONE AND ONLY ONE
10+
// of the simple properties is satisfied; it lazily uses the first one that is satisfied.
11+
// Ergo, it will not return an error if more than one property is valid.
12+
func (o *OneOfMoebiusSchemaStringDoaGddGANK2MA6NR) UnmarshalJSON(bytes []byte) error {
13+
var myMoebiusSchema MoebiusSchema
14+
if err := json.Unmarshal(bytes, &myMoebiusSchema); err == nil {
15+
o.MoebiusSchema = &myMoebiusSchema
16+
return nil
17+
}
18+
var myStringDoaGddGA StringDoaGddGA
19+
if err := json.Unmarshal(bytes, &myStringDoaGddGA); err == nil {
20+
o.StringDoaGddGA = &myStringDoaGddGA
21+
return nil
22+
}
23+
return errors.New("failed to unmarshal one of the object properties")
24+
}
25+
func (o OneOfMoebiusSchemaStringDoaGddGANK2MA6NR) MarshalJSON() ([]byte, error) {
26+
if o.MoebiusSchema != nil {
27+
return json.Marshal(o.MoebiusSchema)
28+
}
29+
if o.StringDoaGddGA != nil {
30+
return json.Marshal(o.StringDoaGddGA)
31+
}
32+
return nil, errors.New("failed to marshal any one of the object properties")
33+
}
134
type MoebiusSchema struct {
2-
MoebiusProperty *MoebiusSchema `json:"MoebiusProperty,omitempty"`
3-
}
35+
MoebiusProperty *MoebiusSchema `json:"MoebiusProperty,omitempty"`
36+
DeeperMobiusProperty *OneOfMoebiusSchemaStringDoaGddGANK2MA6NR `json:"deeperMobiusProperty,omitempty"`
37+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
from typing import TypedDict
22
from typing import Optional
3+
from typing import NewType
4+
from typing import Union
5+
6+
StringDoaGddGA = NewType("StringDoaGddGA", str)
7+
8+
OneOfMoebiusSchemaStringDoaGddGANK2MA6NR = NewType("OneOfMoebiusSchemaStringDoaGddGANK2MA6NR", Union[MoebiusSchema, StringDoaGddGA])
39

410
class MoebiusSchema(TypedDict):
511
MoebiusProperty: Optional[MoebiusSchema]
12+
deeperMobiusProperty: Optional[OneOfMoebiusSchemaStringDoaGddGANK2MA6NR]

src/integration-tests/expecteds/rs/circular-reference.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@ extern crate derive_builder;
44

55
use serde::{Serialize, Deserialize};
66
use derive_builder::Builder;
7+
pub type StringDoaGddGA = String;
8+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
9+
#[serde(untagged)]
10+
pub enum OneOfMoebiusSchemaStringDoaGddGANK2MA6NR {
11+
MoebiusSchema(Box<MoebiusSchema>),
12+
StringDoaGddGA(StringDoaGddGA),
13+
}
714
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Builder, Default)]
815
#[builder(setter(strip_option), default)]
916
#[serde(default)]
1017
pub struct MoebiusSchema {
1118
#[serde(rename = "MoebiusProperty", skip_serializing_if = "Option::is_none")]
1219
pub moebius_property: Option<Box<MoebiusSchema>>,
20+
#[serde(rename = "deeperMobiusProperty", skip_serializing_if = "Option::is_none")]
21+
pub deeper_mobius_property: Option<OneOfMoebiusSchemaStringDoaGddGANK2MA6NR>,
1322
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
export type StringDoaGddGA = string;
2+
export type OneOfMoebiusSchemaStringDoaGddGANK2MA6NR = MoebiusSchema | StringDoaGddGA;
13
export interface MoebiusSchema {
24
MoebiusProperty?: MoebiusSchema;
5+
deeperMobiusProperty?: OneOfMoebiusSchemaStringDoaGddGANK2MA6NR;
36
[k: string]: any;
47
}

src/integration-tests/runner.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ const getTestCaseBase = async (names: string[], languages: string[]): Promise<Te
3737
const testCases: TestCase[] = [];
3838

3939
languages.forEach((language) => {
40-
// if (language !== "ts") { return; }
40+
// if (language !== "rs") { return; }
4141

4242
names.forEach((name) => {
43-
// if (name !== "json-schema-meta-schema") { return; }
43+
// if (name !== "circular-reference") { return; }
4444

4545
promises.push(readFile(`${testCaseDir}/${name}.json`, "utf8")
4646
.then((fileContents) => {

src/integration-tests/test-cases/circular-reference.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
"title": "moebiusSchema",
33
"type": "object",
44
"properties": {
5-
"MoebiusProperty": { "$ref": "#" }
5+
"MoebiusProperty": { "$ref": "#" },
6+
"deeperMobiusProperty": {
7+
"oneOf": [
8+
{ "$ref": "#" },
9+
{
10+
"type": "string"
11+
}
12+
]
13+
}
614
}
715
}

0 commit comments

Comments
 (0)