Skip to content

Commit ab0f182

Browse files
authored
Default impl generation can be broken (#532)
1 parent e6f46fc commit ab0f182

File tree

3 files changed

+126
-13
lines changed

3 files changed

+126
-13
lines changed

typify-impl/src/value.rs

+23-13
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,25 @@ fn value_for_struct_props(
377377
) -> Option<Vec<TokenStream>> {
378378
let map = value.as_object()?;
379379

380+
let direct_props = properties.iter().filter_map(|prop| {
381+
let name = match &prop.rename {
382+
StructPropertyRename::None => &prop.name,
383+
StructPropertyRename::Rename(rename) => rename,
384+
StructPropertyRename::Flatten => return None,
385+
};
386+
387+
let name_ident = format_ident!("{}", &prop.name);
388+
389+
if let Some(value) = map.get(name) {
390+
let type_entry = type_space.id_to_entry.get(&prop.type_id).unwrap();
391+
let prop_value = type_entry.output_value(type_space, value, scope)?;
392+
393+
Some(quote! { #name_ident: #prop_value })
394+
} else {
395+
Some(quote! { #name_ident: Default::default() })
396+
}
397+
});
398+
380399
let prop_map = properties
381400
.iter()
382401
.filter_map(|prop| {
@@ -390,17 +409,6 @@ fn value_for_struct_props(
390409
})
391410
.collect::<BTreeMap<_, _>>();
392411

393-
let direct_props = map.iter().filter_map(|(name, value)| {
394-
// It's okay if the property isn't in the prop_map... it must be part
395-
// of one of the flattened properties.
396-
let prop = prop_map.get(name)?;
397-
let type_entry = type_space.id_to_entry.get(&prop.type_id).unwrap();
398-
let prop_value = type_entry.output_value(type_space, value, scope)?;
399-
let name_ident = format_ident!("{}", name);
400-
401-
Some(quote! { #name_ident: #prop_value })
402-
});
403-
404412
let extra_value = serde_json::Value::Object(
405413
map.clone()
406414
.into_iter()
@@ -622,7 +630,8 @@ mod tests {
622630
super::Test {
623631
a: "aaaa".to_string(),
624632
b: 7_u32,
625-
c: Some("cccc".to_string())
633+
c: Some("cccc".to_string()),
634+
d: Default::default()
626635
}
627636
}
628637
.to_string()
@@ -663,7 +672,8 @@ mod tests {
663672
Test {
664673
a: "aaaa".to_string(),
665674
b: 7_u32,
666-
c: Some("cccc".to_string())
675+
c: Some("cccc".to_string()),
676+
d: Default::default()
667677
}
668678
}
669679
.to_string()

typify/tests/schemas/types-with-defaults.json

+21
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@
2929
}
3030
}
3131
},
32+
"OuterThing": {
33+
"type": "object",
34+
"properties": {
35+
"thing": {
36+
"type": "object",
37+
"title": "ThingWithDefaults",
38+
"properties": {
39+
"a": {
40+
"type": "string"
41+
},
42+
"type": {
43+
"type": "string"
44+
}
45+
},
46+
"additionalProperties": false,
47+
"default": {
48+
"type": "bee"
49+
}
50+
}
51+
}
52+
},
3253
"Doodad": {
3354
"type": "object",
3455
"properties": {

typify/tests/schemas/types-with-defaults.rs

+82
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,44 @@ impl From<&Doodad> for Doodad {
5353
value.clone()
5454
}
5555
}
56+
#[doc = "OuterThing"]
57+
#[doc = r""]
58+
#[doc = r" <details><summary>JSON schema</summary>"]
59+
#[doc = r""]
60+
#[doc = r" ```json"]
61+
#[doc = "{"]
62+
#[doc = " \"type\": \"object\","]
63+
#[doc = " \"properties\": {"]
64+
#[doc = " \"thing\": {"]
65+
#[doc = " \"title\": \"ThingWithDefaults\","]
66+
#[doc = " \"default\": {"]
67+
#[doc = " \"type\": \"bee\""]
68+
#[doc = " },"]
69+
#[doc = " \"type\": \"object\","]
70+
#[doc = " \"properties\": {"]
71+
#[doc = " \"a\": {"]
72+
#[doc = " \"type\": \"string\""]
73+
#[doc = " },"]
74+
#[doc = " \"type\": {"]
75+
#[doc = " \"type\": \"string\""]
76+
#[doc = " }"]
77+
#[doc = " },"]
78+
#[doc = " \"additionalProperties\": false"]
79+
#[doc = " }"]
80+
#[doc = " }"]
81+
#[doc = "}"]
82+
#[doc = r" ```"]
83+
#[doc = r" </details>"]
84+
#[derive(Clone, Debug, Deserialize, Serialize)]
85+
pub struct OuterThing {
86+
#[serde(default, skip_serializing_if = "Option::is_none")]
87+
pub thing: Option<ThingWithDefaults>,
88+
}
89+
impl From<&OuterThing> for OuterThing {
90+
fn from(value: &OuterThing) -> Self {
91+
value.clone()
92+
}
93+
}
5694
#[doc = "TestBed"]
5795
#[doc = r""]
5896
#[doc = r" <details><summary>JSON schema</summary>"]
@@ -99,6 +137,50 @@ impl From<&TestBed> for TestBed {
99137
value.clone()
100138
}
101139
}
140+
#[doc = "ThingWithDefaults"]
141+
#[doc = r""]
142+
#[doc = r" <details><summary>JSON schema</summary>"]
143+
#[doc = r""]
144+
#[doc = r" ```json"]
145+
#[doc = "{"]
146+
#[doc = " \"title\": \"ThingWithDefaults\","]
147+
#[doc = " \"default\": {"]
148+
#[doc = " \"type\": \"bee\""]
149+
#[doc = " },"]
150+
#[doc = " \"type\": \"object\","]
151+
#[doc = " \"properties\": {"]
152+
#[doc = " \"a\": {"]
153+
#[doc = " \"type\": \"string\""]
154+
#[doc = " },"]
155+
#[doc = " \"type\": {"]
156+
#[doc = " \"type\": \"string\""]
157+
#[doc = " }"]
158+
#[doc = " },"]
159+
#[doc = " \"additionalProperties\": false"]
160+
#[doc = "}"]
161+
#[doc = r" ```"]
162+
#[doc = r" </details>"]
163+
#[derive(Clone, Debug, Deserialize, Serialize)]
164+
#[serde(deny_unknown_fields)]
165+
pub struct ThingWithDefaults {
166+
#[serde(default, skip_serializing_if = "Option::is_none")]
167+
pub a: Option<String>,
168+
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
169+
pub type_: Option<String>,
170+
}
171+
impl From<&ThingWithDefaults> for ThingWithDefaults {
172+
fn from(value: &ThingWithDefaults) -> Self {
173+
value.clone()
174+
}
175+
}
176+
impl Default for ThingWithDefaults {
177+
fn default() -> Self {
178+
ThingWithDefaults {
179+
a: Default::default(),
180+
type_: Some("bee".to_string()),
181+
}
182+
}
183+
}
102184
#[doc = r" Generation of default values for serde."]
103185
pub mod defaults {
104186
pub(super) fn doodad_when() -> chrono::DateTime<chrono::offset::Utc> {

0 commit comments

Comments
 (0)