Skip to content

Commit ecba2e1

Browse files
sunshowersepage
authored andcommitted
test: Show broken empty table behavior
In case of an empty table/map, we currently do not resolve to the `Default` impl as we should (specifically for `profile.baz` in the test). A bisect appears to indicate that ec36bff is responsible. The next commit fixes this.
1 parent 9a0c1a1 commit ecba2e1

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed

tests/testsuite/merge.rs

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use snapbox::{assert_data_eq, prelude::*, str};
2+
13
use config::{Config, File, FileFormat, Map};
24

35
#[test]
@@ -80,3 +82,256 @@ fn test_merge_whole_config() {
8082
assert_eq!(config3.get("x").ok(), Some(10));
8183
assert_eq!(config3.get("y").ok(), Some(25));
8284
}
85+
86+
#[test]
87+
#[cfg(feature = "json")]
88+
/// Test a few scenarios with empty maps:
89+
fn test_merge_empty_maps() {
90+
use std::collections::BTreeMap;
91+
92+
#[derive(Debug, Deserialize)]
93+
#[allow(dead_code)] // temporary while this test is broken
94+
struct Settings {
95+
profile: BTreeMap<String, Profile>,
96+
}
97+
98+
#[derive(Debug, Default, Deserialize)]
99+
#[allow(dead_code)] // temporary while this test is broken
100+
struct Profile {
101+
name: Option<String>,
102+
}
103+
104+
// * missing_to_empty: no key -> empty map
105+
let cfg = Config::builder()
106+
.add_source(File::from_str(r#"{ "profile": {} }"#, FileFormat::Json))
107+
.add_source(File::from_str(
108+
r#"{ "profile": { "missing_to_empty": {} } }"#,
109+
FileFormat::Json,
110+
))
111+
.build()
112+
.unwrap();
113+
let res = cfg.try_deserialize::<Settings>();
114+
assert_data_eq!(
115+
res.unwrap_err().to_string(),
116+
str![
117+
"invalid type: unit value, expected struct Profile for key `profile.missing_to_empty`"
118+
]
119+
);
120+
121+
// * missing_to_non_empty: no key -> map with k/v
122+
let cfg = Config::builder()
123+
.add_source(File::from_str(r#"{ "profile": {} }"#, FileFormat::Json))
124+
.add_source(File::from_str(
125+
r#"{ "profile": { "missing_to_non_empty": { "name": "bar" } } }"#,
126+
FileFormat::Json,
127+
))
128+
.build()
129+
.unwrap();
130+
let res = cfg.try_deserialize::<Settings>();
131+
assert_data_eq!(
132+
res.unwrap().to_debug(),
133+
str![[r#"
134+
Settings {
135+
profile: {
136+
"missing_to_non_empty": Profile {
137+
name: Some(
138+
"bar",
139+
),
140+
},
141+
},
142+
}
143+
144+
"#]]
145+
);
146+
147+
// * empty_to_empty: empty map -> empty map
148+
let cfg = Config::builder()
149+
.add_source(File::from_str(
150+
r#"{ "profile": { "empty_to_empty": {} } }"#,
151+
FileFormat::Json,
152+
))
153+
.add_source(File::from_str(
154+
r#"{ "profile": { "empty_to_empty": {} } }"#,
155+
FileFormat::Json,
156+
))
157+
.build()
158+
.unwrap();
159+
let res = cfg.try_deserialize::<Settings>();
160+
assert_data_eq!(
161+
res.unwrap_err().to_string(),
162+
str!["invalid type: unit value, expected struct Profile for key `profile.empty_to_empty`"]
163+
);
164+
165+
// * empty_to_non_empty: empty map -> map with k/v
166+
let cfg = Config::builder()
167+
.add_source(File::from_str(
168+
r#"{ "profile": { "empty_to_non_empty": {} } }"#,
169+
FileFormat::Json,
170+
))
171+
.add_source(File::from_str(
172+
r#"{ "profile": { "empty_to_non_empty": { "name": "bar" } } }"#,
173+
FileFormat::Json,
174+
))
175+
.build()
176+
.unwrap();
177+
let res = cfg.try_deserialize::<Settings>();
178+
assert_data_eq!(
179+
res.unwrap().to_debug(),
180+
str![[r#"
181+
Settings {
182+
profile: {
183+
"empty_to_non_empty": Profile {
184+
name: Some(
185+
"bar",
186+
),
187+
},
188+
},
189+
}
190+
191+
"#]]
192+
);
193+
194+
// * non_empty_to_empty: map with k/v -> empty map
195+
let cfg = Config::builder()
196+
.add_source(File::from_str(
197+
r#"{ "profile": { "non_empty_to_empty": { "name": "foo" } } }"#,
198+
FileFormat::Json,
199+
))
200+
.add_source(File::from_str(
201+
r#"{ "profile": { "non_empty_to_empty": {} } }"#,
202+
FileFormat::Json,
203+
))
204+
.build()
205+
.unwrap();
206+
let res = cfg.try_deserialize::<Settings>();
207+
assert_data_eq!(
208+
res.unwrap().to_debug(),
209+
str![[r#"
210+
Settings {
211+
profile: {
212+
"non_empty_to_empty": Profile {
213+
name: Some(
214+
"foo",
215+
),
216+
},
217+
},
218+
}
219+
220+
"#]]
221+
);
222+
223+
// * non_empty_to_non_empty: map with k/v -> map with k/v (override)
224+
let cfg = Config::builder()
225+
.add_source(File::from_str(
226+
r#"{ "profile": { "non_empty_to_non_empty": { "name": "foo" } } }"#,
227+
FileFormat::Json,
228+
))
229+
.add_source(File::from_str(
230+
r#"{ "profile": { "non_empty_to_non_empty": { "name": "bar" } } }"#,
231+
FileFormat::Json,
232+
))
233+
.build()
234+
.unwrap();
235+
let res = cfg.try_deserialize::<Settings>();
236+
assert_data_eq!(
237+
res.unwrap().to_debug(),
238+
str![[r#"
239+
Settings {
240+
profile: {
241+
"non_empty_to_non_empty": Profile {
242+
name: Some(
243+
"bar",
244+
),
245+
},
246+
},
247+
}
248+
249+
"#]]
250+
);
251+
252+
// * null_to_empty: null -> empty map
253+
// * null_to_non_empty: null -> map with k/v
254+
// * int_to_empty: int -> empty map
255+
// * int_to_non_empty: int -> map with k/v
256+
let cfg = Config::builder()
257+
.add_source(File::from_str(
258+
r#"{ "profile": { "null_to_empty": null } }"#,
259+
FileFormat::Json,
260+
))
261+
.add_source(File::from_str(
262+
r#"{ "profile": { "null_to_empty": {} } }"#,
263+
FileFormat::Json,
264+
))
265+
.build()
266+
.unwrap();
267+
let res = cfg.try_deserialize::<Settings>();
268+
assert_data_eq!(
269+
res.unwrap_err().to_string(),
270+
str!["invalid type: unit value, expected struct Profile for key `profile.null_to_empty`"]
271+
);
272+
273+
// * null_to_non_empty: null -> map with k/v
274+
let cfg = Config::builder()
275+
.add_source(File::from_str(
276+
r#"{ "profile": { "null_to_non_empty": null } }"#,
277+
FileFormat::Json,
278+
))
279+
.add_source(File::from_str(
280+
r#"{ "profile": { "null_to_non_empty": { "name": "bar" } } }"#,
281+
FileFormat::Json,
282+
))
283+
.build()
284+
.unwrap();
285+
let res = cfg.try_deserialize::<Settings>();
286+
assert_data_eq!(
287+
res.unwrap().to_debug(),
288+
str![[r#"
289+
Settings {
290+
profile: {
291+
"null_to_non_empty": Profile {
292+
name: Some(
293+
"bar",
294+
),
295+
},
296+
},
297+
}
298+
299+
"#]]
300+
);
301+
302+
// * int_to_empty: int -> empty map
303+
let cfg = Config::builder()
304+
.add_source(File::from_str(
305+
r#"{ "profile": { "int_to_empty": 42 } }"#,
306+
FileFormat::Json,
307+
))
308+
.add_source(File::from_str(
309+
r#"{ "profile": { "int_to_empty": {} } }"#,
310+
FileFormat::Json,
311+
))
312+
.build()
313+
.unwrap();
314+
let res = cfg.try_deserialize::<Settings>();
315+
assert_data_eq!(
316+
res.unwrap_err().to_string(),
317+
str!["invalid type: integer `42`, expected struct Profile for key `profile.int_to_empty`"]
318+
);
319+
320+
// * int_to_non_empty: int -> map with k/v
321+
let cfg = Config::builder()
322+
.add_source(File::from_str(
323+
r#"{ "profile": { "int_to_non_empty": 42 } }"#,
324+
FileFormat::Json,
325+
))
326+
.add_source(File::from_str(
327+
r#"{ "int_to_non_empty": { "name": "bar" } }"#,
328+
FileFormat::Json,
329+
))
330+
.build()
331+
.unwrap();
332+
let res = cfg.try_deserialize::<Settings>();
333+
assert_data_eq!(
334+
res.unwrap_err().to_string(),
335+
str!["invalid type: integer `42`, expected struct Profile for key `profile.int_to_non_empty`"]
336+
);
337+
}

0 commit comments

Comments
 (0)