Skip to content

Commit eece263

Browse files
committed
Merge remote-tracking branch 'origin/master' into feature/add_custom_use_and_derive
2 parents dc0bf81 + b7c58fc commit eece263

File tree

18 files changed

+102
-38
lines changed

18 files changed

+102
-38
lines changed

.github/workflows/publish.yml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44

55
jobs:
66
release_binary:
7+
permissions: write-all
78
runs-on: ${{ matrix.runs_on }}
89
strategy:
910
matrix:

crates/core/src/codegen/mod.rs

+56-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::target::{
99
use ast::{Ast, SchemaAst};
1010
use jtd::Schema;
1111
use namespace::Namespace;
12-
use std::collections::BTreeMap;
12+
use std::collections::{BTreeMap, BTreeSet};
1313
use std::fs::File;
1414
use std::io::Write;
1515
use std::path::Path;
@@ -36,6 +36,7 @@ struct CodeGenerator<'a, T> {
3636
out_dir: &'a Path,
3737
strategy: Strategy,
3838
definition_names: BTreeMap<String, String>,
39+
recursive_definitions: BTreeSet<String>,
3940
}
4041

4142
struct FileData<T> {
@@ -50,6 +51,7 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
5051
out_dir,
5152
strategy: target.strategy(),
5253
definition_names: BTreeMap::new(),
54+
recursive_definitions: BTreeSet::new(),
5355
}
5456
}
5557

@@ -68,6 +70,18 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
6870
self.definition_names.insert(name.clone(), ast_name);
6971
}
7072

73+
// Detect recursive definitions, as some target language need to handle
74+
// them specifically (e.g. Rust).
75+
// Note that a type is *not* considered to be recursive it it contains
76+
// instances of itself only through "elements" or "values"
77+
// (the underlying container is considered to break the recursion).
78+
for (name, ast) in &schema_ast.definitions {
79+
let mut visited = vec![];
80+
if find_recursion(name, ast, &schema_ast.definitions, &mut visited) {
81+
self.recursive_definitions.insert(name.clone());
82+
}
83+
}
84+
7185
// If the target is using FilePerType partitioning, then this state
7286
// won't actually be used at all. If it's using SingleFile partitioning,
7387
// then this is the only file state that will be used.
@@ -120,8 +134,17 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
120134
// Ref nodes are a special sort of "expr-like" node, where we
121135
// already know what the name of the expression is; it's the name of
122136
// the definition.
123-
Ast::Ref { definition, .. } => self.definition_names[&definition].clone(),
124-
137+
// Note however that recursive definition may need some special
138+
// treatment by the target.
139+
Ast::Ref { metadata, definition } => {
140+
let sub_expr = self.definition_names[&definition].clone();
141+
if self.recursive_definitions.iter().any(|i| i == &definition) {
142+
self.target
143+
.expr(&mut file_data.state, metadata, Expr::RecursiveRef(sub_expr))
144+
} else {
145+
sub_expr
146+
}
147+
}
125148
// The remaining "expr-like" node types just build up strings and
126149
// possibly alter the per-file state (usually in order to add
127150
// "imports" to the file).
@@ -479,3 +502,33 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
479502
Ok(())
480503
}
481504
}
505+
506+
fn find_recursion(name: &str, ast: &Ast, definitions: &BTreeMap<String, Ast>, visited: &mut Vec<String>) -> bool {
507+
match ast {
508+
Ast::Ref { definition, .. } => {
509+
if definition == name {
510+
true
511+
} else if visited.iter().any(|i| i == &name) {
512+
false
513+
} else if let Some(ast2) = definitions.get(definition) {
514+
visited.push(definition.clone());
515+
find_recursion(name, &ast2, definitions, visited)
516+
} else {
517+
false
518+
}
519+
}
520+
Ast::NullableOf { type_, .. } => {
521+
find_recursion(name, type_, definitions, visited)
522+
}
523+
Ast::Struct { fields, .. } => {
524+
fields.iter()
525+
.any(|f| find_recursion(name, &f.type_, definitions, visited))
526+
}
527+
Ast::Discriminator { variants, .. } => {
528+
variants.iter()
529+
.flat_map(|v| v.fields.iter())
530+
.any(|f| find_recursion(name, &f.type_, definitions, visited))
531+
}
532+
_ => false,
533+
}
534+
}

crates/core/src/target/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub enum Expr {
8787
ArrayOf(String),
8888
DictOf(String),
8989
NullableOf(String),
90+
RecursiveRef(String),
9091
}
9192

9293
#[derive(Debug)]

crates/target_csharp_system_text/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl jtd_codegen::target::Target for Target {
125125
format!("IDictionary<string, {}>", sub_expr)
126126
}
127127
target::Expr::NullableOf(sub_expr) => format!("{}?", sub_expr),
128+
target::Expr::RecursiveRef(sub_expr) => sub_expr,
128129
}
129130
}
130131

crates/target_go/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ impl jtd_codegen::target::Target for Target {
114114
target::Expr::ArrayOf(sub_expr) => format!("[]{}", sub_expr),
115115
target::Expr::DictOf(sub_expr) => format!("map[string]{}", sub_expr),
116116
target::Expr::NullableOf(sub_expr) => format!("*{}", sub_expr),
117+
target::Expr::RecursiveRef(sub_expr) => sub_expr,
117118
}
118119
}
119120

crates/target_java_jackson/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl jtd_codegen::target::Target for Target {
125125
format!("Map<String, {}>", sub_expr)
126126
}
127127
target::Expr::NullableOf(sub_expr) => sub_expr, // everything is already nullable
128+
target::Expr::RecursiveRef(sub_expr) => sub_expr,
128129
}
129130
}
130131

crates/target_python/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ impl jtd_codegen::target::Target for Target {
135135

136136
format!("Optional[{}]", sub_expr)
137137
}
138+
target::Expr::RecursiveRef(sub_expr) => sub_expr,
138139
}
139140
}
140141

crates/target_ruby/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ impl jtd_codegen::target::Target for Target {
117117
format!("Hash[String, {}]", sub_expr)
118118
}
119119
target::Expr::NullableOf(sub_expr) => sub_expr,
120+
target::Expr::RecursiveRef(sub_expr) => sub_expr,
120121
}
121122
}
122123

crates/target_ruby_sig/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ impl jtd_codegen::target::Target for Target {
116116
format!("Hash[String, {}]", sub_expr)
117117
}
118118
target::Expr::NullableOf(sub_expr) => format!("{}?", sub_expr),
119+
target::Expr::RecursiveRef(sub_expr) => sub_expr,
119120
}
120121
}
121122

crates/target_rust/output/custom_overrides/mod.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ pub struct RootOverrideTypeDiscriminatorBaz {}
99
#[derive(Serialize, Deserialize)]
1010
pub struct Root {
1111
#[serde(rename = "override_elements_container")]
12-
pub overrideElementsContainer: Vec<String>,
12+
pub override_elements_container: Vec<String>,
1313

1414
#[serde(rename = "override_type_discriminator")]
15-
pub overrideTypeDiscriminator: serde_json::Value,
15+
pub override_type_discriminator: serde_json::Value,
1616

1717
#[serde(rename = "override_type_enum")]
18-
pub overrideTypeEnum: serde_json::Value,
18+
pub override_type_enum: serde_json::Value,
1919

2020
#[serde(rename = "override_type_expr")]
21-
pub overrideTypeExpr: serde_json::Value,
21+
pub override_type_expr: serde_json::Value,
2222

2323
#[serde(rename = "override_type_properties")]
24-
pub overrideTypeProperties: serde_json::Value,
24+
pub override_type_properties: serde_json::Value,
2525

2626
#[serde(rename = "override_values_container")]
27-
pub overrideValuesContainer: HashMap<String, String>,
27+
pub override_values_container: HashMap<String, String>,
2828
}

crates/target_rust/output/description/mod.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,31 @@ pub struct RootPropertiesWithDescription {}
3838
pub struct Root {
3939
/// A description for discriminator
4040
#[serde(rename = "discriminator_with_description")]
41-
pub discriminatorWithDescription: RootDiscriminatorWithDescription,
41+
pub discriminator_with_description: RootDiscriminatorWithDescription,
4242

4343
/// A description for enum
4444
#[serde(rename = "enum_with_description")]
45-
pub enumWithDescription: RootEnumWithDescription,
45+
pub enum_with_description: RootEnumWithDescription,
4646

4747
/// Whereas disregard and contempt for human rights have resulted in
4848
/// barbarous acts which have outraged the conscience of mankind, and the
4949
/// advent of a world in which human beings shall enjoy freedom of speech
5050
/// and belief and freedom from fear and want has been proclaimed as the
5151
/// highest aspiration of the common people,
5252
#[serde(rename = "long_description")]
53-
pub longDescription: String,
53+
pub long_description: String,
5454

5555
/// A description for properties
5656
#[serde(rename = "properties_with_description")]
57-
pub propertiesWithDescription: RootPropertiesWithDescription,
57+
pub properties_with_description: RootPropertiesWithDescription,
5858

5959
/// A description for ref
6060
#[serde(rename = "ref_with_description")]
61-
pub refWithDescription: Baz,
61+
pub ref_with_description: Baz,
6262

6363
/// A description for string
6464
#[serde(rename = "string_with_description")]
65-
pub stringWithDescription: String,
65+
pub string_with_description: String,
6666
}
6767

6868
/// A description for a definition

crates/target_rust/output/empty_and_nonascii_properties/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
55
#[derive(Serialize, Deserialize)]
66
pub struct Root {
77
#[serde(rename = "")]
8-
pub defaultName: String,
8+
pub default_name: String,
99

1010
#[serde(rename = "$foo")]
1111
pub foo: String,
@@ -17,14 +17,14 @@ pub struct Root {
1717
pub foo1: String,
1818

1919
#[serde(rename = "foo\nbar")]
20-
pub fooBar: String,
20+
pub foo_bar: String,
2121

2222
#[serde(rename = "foo bar")]
23-
pub fooBar0: String,
23+
pub foo_bar0: String,
2424

2525
#[serde(rename = "foo0bar")]
2626
pub foo0bar: String,
2727

2828
#[serde(rename = "foo﷽bar")]
29-
pub fooBar1: String,
29+
pub foo_bar1: String,
3030
}

crates/target_rust/output/enum_collisions/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ pub struct Root {
3232
pub foo: RootFoo,
3333

3434
#[serde(rename = "foo_bar")]
35-
pub fooBar: RootFooBar0,
35+
pub foo_bar: RootFooBar0,
3636
}

crates/target_rust/output/initialisms/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ pub struct Root {
2020
pub id: String,
2121

2222
#[serde(rename = "nested_id_initialism")]
23-
pub nestedIdInitialism: RootNestedIdInitialism,
23+
pub nested_id_initialism: RootNestedIdInitialism,
2424

2525
#[serde(rename = "utf8")]
2626
pub utf8: String,
2727

2828
#[serde(rename = "word_with_embedded_id_initialism")]
29-
pub wordWithEmbeddedIdInitialism: String,
29+
pub word_with_embedded_id_initialism: String,
3030

3131
#[serde(rename = "word_with_trailing_initialism_id")]
32-
pub wordWithTrailingInitialismId: String,
32+
pub word_with_trailing_initialism_id: String,
3333
}

crates/target_rust/output/nullable_references/mod.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@ use serde::{Deserialize, Serialize};
55
#[derive(Serialize, Deserialize)]
66
pub struct Root {
77
#[serde(rename = "notnull_ref_notnull_string")]
8-
pub notnullRefNotnullString: NotnullRefNotnullString,
8+
pub notnull_ref_notnull_string: NotnullRefNotnullString,
99

1010
#[serde(rename = "notnull_ref_null_string")]
11-
pub notnullRefNullString: NotnullRefNullString,
11+
pub notnull_ref_null_string: NotnullRefNullString,
1212

1313
#[serde(rename = "notnull_string")]
14-
pub notnullString: NotnullString,
14+
pub notnull_string: NotnullString,
1515

1616
#[serde(rename = "null_ref_notnull_string")]
17-
pub nullRefNotnullString: NullRefNotnullString,
17+
pub null_ref_notnull_string: NullRefNotnullString,
1818

1919
#[serde(rename = "null_ref_null_string")]
20-
pub nullRefNullString: NullRefNullString,
20+
pub null_ref_null_string: NullRefNullString,
2121

2222
#[serde(rename = "null_string")]
23-
pub nullString: NullString,
23+
pub null_string: NullString,
2424
}
2525

2626
pub type NotnullRefNotnullString = NotnullString;

crates/target_rust/output/type_collisions/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ pub struct Root {
2626
pub foo: RootFoo,
2727

2828
#[serde(rename = "foo_bar")]
29-
pub fooBar: RootFooBar0,
29+
pub foo_bar: RootFooBar0,
3030
}

crates/target_rust/src/lib.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ lazy_static! {
1919
static ref FIELD_NAMING_CONVENTION: Box<dyn inflect::Inflector + Send + Sync> =
2020
Box::new(inflect::KeywordAvoidingInflector::new(
2121
KEYWORDS.clone(),
22-
inflect::TailInflector::new(inflect::Case::camel_case())
22+
inflect::TailInflector::new(inflect::Case::snake_case())
2323
));
2424
static ref ENUM_MEMBER_NAMING_CONVENTION: Box<dyn inflect::Inflector + Send + Sync> =
2525
Box::new(inflect::KeywordAvoidingInflector::new(
@@ -122,14 +122,16 @@ impl jtd_codegen::target::Target for Target {
122122
format!("HashMap<String, {}>", sub_expr)
123123
}
124124

125-
// TODO: A Box here is necessary because otherwise a recursive data
126-
// structure may fail to compile, such as in the geojson test case.
125+
target::Expr::NullableOf(sub_expr) => format!("Option<{}>", sub_expr),
126+
// A Box here is usually necessary for recursive data structures,
127+
// such as in the geojson test case.
127128
//
128-
// A more "proper" fix to this problem would be to have a cyclical
129-
// reference detector, and insert Box<T> only if it's necessary to
130-
// break a cyclic dependency. It's unclear how much of a problem
131-
// this is in the real world.
132-
target::Expr::NullableOf(sub_expr) => format!("Option<Box<{}>>", sub_expr),
129+
// Note that this strategy is slighyly over-defensive;
130+
// in a cycle of mutually recursive types,
131+
// only one of the types needs to be boxed to break the cycle.
132+
// In such cases, the user may want to optimize the code,
133+
// overriding some of the boxings with metadata.rustType.
134+
target::Expr::RecursiveRef(sub_expr) => format!("Box<{}>", sub_expr),
133135
}
134136
}
135137

crates/target_typescript/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ impl jtd_codegen::target::Target for Target {
9292
target::Expr::ArrayOf(sub_expr) => format!("{}[]", sub_expr),
9393
target::Expr::DictOf(sub_expr) => format!("{{ [key: string]: {} }}", sub_expr),
9494
target::Expr::NullableOf(sub_expr) => format!("({} | null)", sub_expr),
95+
target::Expr::RecursiveRef(sub_expr) => sub_expr,
9596
}
9697
}
9798

0 commit comments

Comments
 (0)