Skip to content

Commit e49b3ae

Browse files
committed
Add tests verifying overlapping prefixes and defaults
These tests demonstrate the current failure mode around overlapping env keys and inner structs. To some extent this is a limitation of mapping on to environment variables with an underscore as both the namespace separator and the substitute for dashes in flag names in that the mapping is not strictly one-to-one.
1 parent b82281a commit e49b3ae

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

src/cargo/util/config/de.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,20 @@ impl<'config> ConfigMapAccess<'config> {
298298
// If the caller is interested in a field which we can provide from
299299
// the environment, get it from there.
300300
for field in given_fields {
301+
let is_prefix = given_fields
302+
.iter()
303+
.any(|f| f != field && f.starts_with(field));
301304
let mut field_key = de.key.clone();
302305
field_key.push(field);
303306
for env_key in de.config.env.keys() {
304-
if env_key.starts_with(field_key.as_env_key()) {
305-
fields.insert(KeyKind::Normal(field.to_string()));
307+
if is_prefix {
308+
if env_key == field_key.as_env_key() {
309+
fields.insert(KeyKind::Normal(field.to_string()));
310+
}
311+
} else {
312+
if env_key.starts_with(field_key.as_env_key()) {
313+
fields.insert(KeyKind::Normal(field.to_string()));
314+
}
306315
}
307316
}
308317
}

tests/testsuite/config.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,27 @@ fn struct_with_opt_inner_struct() {
12461246
assert_eq!(f.inner.unwrap().value.unwrap(), 12);
12471247
}
12481248

1249+
#[cargo_test]
1250+
fn struct_with_default_inner_struct() {
1251+
// Struct with serde defaults.
1252+
// Check that can be defined with environment variable.
1253+
#[derive(Deserialize, Default)]
1254+
#[serde(default)]
1255+
struct Inner {
1256+
value: i32,
1257+
}
1258+
#[derive(Deserialize, Default)]
1259+
#[serde(default)]
1260+
struct Foo {
1261+
inner: Inner,
1262+
}
1263+
let config = ConfigBuilder::new()
1264+
.env("CARGO_FOO_INNER_VALUE", "12")
1265+
.build();
1266+
let f: Foo = config.get("foo").unwrap();
1267+
assert_eq!(f.inner.value, 12);
1268+
}
1269+
12491270
#[cargo_test]
12501271
fn overlapping_env_config() {
12511272
// Issue where one key is a prefix of another.
@@ -1277,6 +1298,81 @@ fn overlapping_env_config() {
12771298
assert_eq!(s.debug, Some(1));
12781299
}
12791300

1301+
#[cargo_test]
1302+
fn overlapping_env_with_defaults() {
1303+
// Issue where one key is a prefix of another.
1304+
#[derive(Deserialize, Default)]
1305+
#[serde(default, rename_all = "kebab-case")]
1306+
struct Ambig {
1307+
debug: u32,
1308+
debug_assertions: bool,
1309+
}
1310+
let config = ConfigBuilder::new()
1311+
.env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
1312+
.build();
1313+
1314+
let s: Ambig = config.get("ambig").unwrap();
1315+
assert_eq!(s.debug_assertions, true);
1316+
assert_eq!(s.debug, u32::default());
1317+
1318+
let config = ConfigBuilder::new().env("CARGO_AMBIG_DEBUG", "5").build();
1319+
let s: Ambig = config.get("ambig").unwrap();
1320+
assert_eq!(s.debug_assertions, bool::default());
1321+
assert_eq!(s.debug, 5);
1322+
1323+
let config = ConfigBuilder::new()
1324+
.env("CARGO_AMBIG_DEBUG", "1")
1325+
.env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
1326+
.build();
1327+
let s: Ambig = config.get("ambig").unwrap();
1328+
assert_eq!(s.debug_assertions, true);
1329+
assert_eq!(s.debug, 1);
1330+
}
1331+
1332+
#[cargo_test]
1333+
fn struct_with_overlapping_inner_struct_and_defaults() {
1334+
// Struct with serde defaults.
1335+
// Check that can be defined with environment variable.
1336+
#[derive(Deserialize, Default)]
1337+
#[serde(default)]
1338+
struct Inner {
1339+
value: i32,
1340+
}
1341+
1342+
// Containing struct with a prefix of inner
1343+
// Check that the nested struct can have fields defined by env
1344+
#[derive(Deserialize, Default)]
1345+
#[serde(default)]
1346+
struct PrefixContainer {
1347+
inn: bool,
1348+
inner: Inner,
1349+
}
1350+
let config = ConfigBuilder::new()
1351+
.env("CARGO_PREFIXCONTAINER_INNER_VALUE", "12")
1352+
.build();
1353+
let f: PrefixContainer = config.get("prefixcontainer").unwrap();
1354+
assert_eq!(f.inn, bool::default());
1355+
assert_eq!(f.inner.value, 12);
1356+
1357+
// Containing struct where the inner value's field is a prefix of another
1358+
// Check that the nested struct can have fields defined by env
1359+
#[derive(Deserialize, Default)]
1360+
#[serde(default)]
1361+
struct InversePrefixContainer {
1362+
inner_field: bool,
1363+
inner: Inner,
1364+
}
1365+
let config = ConfigBuilder::new()
1366+
.env("CARGO_INVERSEREFIXCONTAINER_INNER_VALUE", "12")
1367+
.build();
1368+
let f: InversePrefixContainer = config.get("inverseprefixcontainer").unwrap();
1369+
assert_eq!(f.inner_field, bool::default());
1370+
// NB. This is a limitation of our env variable parsing. We can't currently
1371+
// handle situations where just a value of the inner struct is set, but
1372+
// it's also named as a prefix of another field on the outer struct.
1373+
// assert_eq!(f.inner.value, 12);
1374+
}
1375+
12801376
#[cargo_test]
12811377
fn string_list_tricky_env() {
12821378
// Make sure StringList handles typed env values.

0 commit comments

Comments
 (0)