Skip to content

Commit 96455a1

Browse files
committed
fix(gctx): provide a richer error context for array item key path
1 parent 63a524a commit 96455a1

File tree

5 files changed

+187
-56
lines changed

5 files changed

+187
-56
lines changed

src/cargo/util/context/de.rs

Lines changed: 89 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
//!
2222
//! [`ConfigValue`]: CV
2323
24+
use crate::util::context::key::ArrayItemKeyPath;
2425
use crate::util::context::value;
2526
use crate::util::context::{ConfigError, ConfigKey, GlobalContext};
2627
use crate::util::context::{ConfigValue as CV, Definition, Value};
@@ -425,12 +426,13 @@ impl<'de, 'gctx> de::MapAccess<'de> for ConfigMapAccess<'gctx> {
425426
}
426427
}
427428

428-
struct ConfigSeqAccess {
429-
list_iter: vec::IntoIter<CV>,
429+
struct ConfigSeqAccess<'gctx> {
430+
de: Deserializer<'gctx>,
431+
list_iter: std::iter::Enumerate<vec::IntoIter<CV>>,
430432
}
431433

432-
impl ConfigSeqAccess {
433-
fn new(de: Deserializer<'_>) -> Result<ConfigSeqAccess, ConfigError> {
434+
impl ConfigSeqAccess<'_> {
435+
fn new(de: Deserializer<'_>) -> Result<ConfigSeqAccess<'_>, ConfigError> {
434436
let mut res = Vec::new();
435437

436438
match de.gctx.get_cv(&de.key)? {
@@ -446,27 +448,44 @@ impl ConfigSeqAccess {
446448
de.gctx.get_env_list(&de.key, &mut res)?;
447449

448450
Ok(ConfigSeqAccess {
449-
list_iter: res.into_iter(),
451+
de,
452+
list_iter: res.into_iter().enumerate(),
450453
})
451454
}
452455
}
453456

454-
impl<'de> de::SeqAccess<'de> for ConfigSeqAccess {
457+
impl<'de, 'gctx> de::SeqAccess<'de> for ConfigSeqAccess<'gctx> {
455458
type Error = ConfigError;
456459

457460
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
458461
where
459462
T: de::DeserializeSeed<'de>,
460463
{
461464
match self.list_iter.next() {
462-
Some(val) => seed.deserialize(ArrayItemDeserializer::new(val)).map(Some),
465+
Some((i, cv)) => {
466+
let mut key_path = ArrayItemKeyPath::new(self.de.key.clone());
467+
let definition = Some(cv.definition().clone());
468+
let de = ArrayItemDeserializer {
469+
cv,
470+
key_path: &mut key_path,
471+
};
472+
seed.deserialize(de)
473+
.map_err(|e| {
474+
// This along with ArrayItemKeyPath provide a better error context of the
475+
// ConfigValue definition + the key path within an array item that native
476+
// TOML key path can't express. For example, `foo.bar[3].baz`.
477+
key_path.push_index(i);
478+
e.with_array_item_key_context(&key_path, definition)
479+
})
480+
.map(Some)
481+
}
463482
None => Ok(None),
464483
}
465484
}
466485
}
467486

468487
/// Source of data for [`ValueDeserializer`]
469-
enum ValueSource<'gctx> {
488+
enum ValueSource<'gctx, 'err> {
470489
/// The deserializer used to actually deserialize a Value struct.
471490
Deserializer {
472491
de: Deserializer<'gctx>,
@@ -478,11 +497,14 @@ enum ValueSource<'gctx> {
478497
/// such as a value inside an array.
479498
/// The [`ConfigSeqAccess`] doesn't know what type it should deserialize to
480499
/// so [`ArrayItemDeserializer`] needs to be able to handle all of them.
481-
ConfigValue(CV),
500+
ConfigValue {
501+
cv: CV,
502+
key_path: &'err mut ArrayItemKeyPath,
503+
},
482504
}
483505

484-
impl<'gctx> ValueSource<'gctx> {
485-
fn with_deserializer(de: Deserializer<'gctx>) -> Result<ValueSource<'gctx>, ConfigError> {
506+
impl<'gctx, 'err> ValueSource<'gctx, 'err> {
507+
fn with_deserializer(de: Deserializer<'gctx>) -> Result<ValueSource<'gctx, 'err>, ConfigError> {
486508
// Figure out where this key is defined.
487509
let definition = {
488510
let env = de.key.as_env_key();
@@ -507,8 +529,8 @@ impl<'gctx> ValueSource<'gctx> {
507529
Ok(Self::Deserializer { de, definition })
508530
}
509531

510-
fn with_cv(cv: CV) -> Self {
511-
Self::ConfigValue(cv)
532+
fn with_cv(cv: CV, key_path: &'err mut ArrayItemKeyPath) -> ValueSource<'gctx, 'err> {
533+
ValueSource::ConfigValue { cv, key_path }
512534
}
513535
}
514536

@@ -519,25 +541,25 @@ impl<'gctx> ValueSource<'gctx> {
519541
/// fields into the location that this configuration value was defined in.
520542
///
521543
/// See more comments in `value.rs` for the protocol used here.
522-
struct ValueDeserializer<'gctx> {
544+
struct ValueDeserializer<'gctx, 'err> {
523545
hits: u32,
524-
source: ValueSource<'gctx>,
546+
source: ValueSource<'gctx, 'err>,
525547
}
526548

527-
impl<'gctx> ValueDeserializer<'gctx> {
528-
fn new(source: ValueSource<'gctx>) -> ValueDeserializer<'gctx> {
549+
impl<'gctx, 'err> ValueDeserializer<'gctx, 'err> {
550+
fn new(source: ValueSource<'gctx, 'err>) -> ValueDeserializer<'gctx, 'err> {
529551
Self { hits: 0, source }
530552
}
531553

532554
fn definition(&self) -> &Definition {
533555
match &self.source {
534556
ValueSource::Deserializer { definition, .. } => definition,
535-
ValueSource::ConfigValue(cv) => cv.definition(),
557+
ValueSource::ConfigValue { cv, .. } => cv.definition(),
536558
}
537559
}
538560
}
539561

540-
impl<'de, 'gctx> de::MapAccess<'de> for ValueDeserializer<'gctx> {
562+
impl<'de, 'gctx, 'err> de::MapAccess<'de> for ValueDeserializer<'gctx, 'err> {
541563
type Error = ConfigError;
542564

543565
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
@@ -563,12 +585,16 @@ impl<'de, 'gctx> de::MapAccess<'de> for ValueDeserializer<'gctx> {
563585
// If this is the first time around we deserialize the `value` field
564586
// which is the actual deserializer
565587
if self.hits == 1 {
566-
return match &self.source {
588+
return match &mut self.source {
567589
ValueSource::Deserializer { de, definition } => seed
568590
.deserialize(de.clone())
569591
.map_err(|e| e.with_key_context(&de.key, Some(definition.clone()))),
570-
ValueSource::ConfigValue(cv) => {
571-
seed.deserialize(ArrayItemDeserializer::new(cv.clone()))
592+
ValueSource::ConfigValue { cv, key_path } => {
593+
let de = ArrayItemDeserializer {
594+
cv: cv.clone(),
595+
key_path,
596+
};
597+
seed.deserialize(de)
572598
}
573599
};
574600
}
@@ -598,18 +624,12 @@ impl<'de, 'gctx> de::MapAccess<'de> for ValueDeserializer<'gctx> {
598624
///
599625
/// It is implemented to handle any types inside a sequence, like `Vec<String>`,
600626
/// `Vec<Value<i32>>`, or even `Vev<HashMap<String, Vec<bool>>>`.
601-
#[derive(Clone)]
602-
struct ArrayItemDeserializer {
627+
struct ArrayItemDeserializer<'err> {
603628
cv: CV,
629+
key_path: &'err mut ArrayItemKeyPath,
604630
}
605631

606-
impl ArrayItemDeserializer {
607-
fn new(cv: CV) -> Self {
608-
Self { cv }
609-
}
610-
}
611-
612-
impl<'de> de::Deserializer<'de> for ArrayItemDeserializer {
632+
impl<'de, 'err> de::Deserializer<'de> for ArrayItemDeserializer<'err> {
613633
type Error = ConfigError;
614634

615635
fn deserialize_struct<V>(
@@ -626,10 +646,14 @@ impl<'de> de::Deserializer<'de> for ArrayItemDeserializer {
626646
//
627647
// See more comments in `value.rs` for the protocol used here.
628648
if name == value::NAME && fields == value::FIELDS {
629-
let source = ValueSource::with_cv(self.cv);
649+
let source = ValueSource::with_cv(self.cv, self.key_path);
630650
return visitor.visit_map(ValueDeserializer::new(source));
631651
}
632-
visitor.visit_map(ArrayItemMapAccess::with_struct(self.cv, fields))
652+
visitor.visit_map(ArrayItemMapAccess::with_struct(
653+
self.cv,
654+
fields,
655+
self.key_path,
656+
))
633657
}
634658

635659
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
@@ -640,8 +664,8 @@ impl<'de> de::Deserializer<'de> for ArrayItemDeserializer {
640664
CV::String(s, _) => visitor.visit_string(s),
641665
CV::Integer(i, _) => visitor.visit_i64(i),
642666
CV::Boolean(b, _) => visitor.visit_bool(b),
643-
l @ CV::List(_, _) => visitor.visit_seq(ArrayItemSeqAccess::new(l)),
644-
t @ CV::Table(_, _) => visitor.visit_map(ArrayItemMapAccess::new(t)),
667+
l @ CV::List(_, _) => visitor.visit_seq(ArrayItemSeqAccess::new(l, self.key_path)),
668+
t @ CV::Table(_, _) => visitor.visit_map(ArrayItemMapAccess::new(t, self.key_path)),
645669
}
646670
}
647671

@@ -654,46 +678,53 @@ impl<'de> de::Deserializer<'de> for ArrayItemDeserializer {
654678
}
655679

656680
/// Sequence access for nested arrays within [`ArrayItemDeserializer`]
657-
struct ArrayItemSeqAccess {
658-
items: vec::IntoIter<CV>,
681+
struct ArrayItemSeqAccess<'err> {
682+
items: std::iter::Enumerate<vec::IntoIter<CV>>,
683+
key_path: &'err mut ArrayItemKeyPath,
659684
}
660685

661-
impl ArrayItemSeqAccess {
662-
fn new(cv: CV) -> Self {
686+
impl<'err> ArrayItemSeqAccess<'err> {
687+
fn new(cv: CV, key_path: &'err mut ArrayItemKeyPath) -> ArrayItemSeqAccess<'err> {
663688
let items = match cv {
664-
CV::List(list, _) => list.into_iter(),
689+
CV::List(list, _) => list.into_iter().enumerate(),
665690
_ => unreachable!("must be a list"),
666691
};
667-
Self { items }
692+
Self { items, key_path }
668693
}
669694
}
670695

671-
impl<'de> de::SeqAccess<'de> for ArrayItemSeqAccess {
696+
impl<'de, 'err> de::SeqAccess<'de> for ArrayItemSeqAccess<'err> {
672697
type Error = ConfigError;
673698

674699
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
675700
where
676701
T: de::DeserializeSeed<'de>,
677702
{
678703
match self.items.next() {
679-
Some(cv) => {
680-
let deserializer = ArrayItemDeserializer::new(cv);
681-
seed.deserialize(deserializer).map(Some)
704+
Some((i, cv)) => {
705+
let de = ArrayItemDeserializer {
706+
cv,
707+
key_path: self.key_path,
708+
};
709+
seed.deserialize(de)
710+
.inspect_err(|_| self.key_path.push_index(i))
711+
.map(Some)
682712
}
683713
None => Ok(None),
684714
}
685715
}
686716
}
687717

688718
/// Map access for nested tables within [`ArrayItemDeserializer`]
689-
struct ArrayItemMapAccess {
719+
struct ArrayItemMapAccess<'err> {
690720
cv: CV,
691721
keys: vec::IntoIter<String>,
692722
current_key: Option<String>,
723+
key_path: &'err mut ArrayItemKeyPath,
693724
}
694725

695-
impl ArrayItemMapAccess {
696-
fn new(cv: CV) -> Self {
726+
impl<'err> ArrayItemMapAccess<'err> {
727+
fn new(cv: CV, key_path: &'err mut ArrayItemKeyPath) -> Self {
697728
let keys = match &cv {
698729
CV::Table(map, _) => map.keys().cloned().collect::<Vec<_>>().into_iter(),
699730
_ => unreachable!("must be a map"),
@@ -702,10 +733,11 @@ impl ArrayItemMapAccess {
702733
cv,
703734
keys,
704735
current_key: None,
736+
key_path,
705737
}
706738
}
707739

708-
fn with_struct(cv: CV, given_fields: &[&str]) -> Self {
740+
fn with_struct(cv: CV, given_fields: &[&str], key_path: &'err mut ArrayItemKeyPath) -> Self {
709741
// TODO: We might want to warn unused fields,
710742
// like what we did in ConfigMapAccess::new_struct
711743
let keys = given_fields
@@ -717,11 +749,12 @@ impl ArrayItemMapAccess {
717749
cv,
718750
keys,
719751
current_key: None,
752+
key_path,
720753
}
721754
}
722755
}
723756

724-
impl<'de> de::MapAccess<'de> for ArrayItemMapAccess {
757+
impl<'de, 'err> de::MapAccess<'de> for ArrayItemMapAccess<'err> {
725758
type Error = ConfigError;
726759

727760
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
@@ -745,8 +778,12 @@ impl<'de> de::MapAccess<'de> for ArrayItemMapAccess {
745778
match &self.cv {
746779
CV::Table(map, _) => {
747780
if let Some(cv) = map.get(&key) {
748-
let deserializer = ArrayItemDeserializer::new(cv.clone());
749-
seed.deserialize(deserializer)
781+
let de = ArrayItemDeserializer {
782+
cv: cv.clone(),
783+
key_path: self.key_path,
784+
};
785+
seed.deserialize(de)
786+
.inspect_err(|_| self.key_path.push_key(key))
750787
} else {
751788
Err(ConfigError::new(
752789
format!("missing config key `{key}`"),

src/cargo/util/context/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fmt;
33
use crate::util::ConfigValue;
44
use crate::util::context::ConfigKey;
55
use crate::util::context::Definition;
6+
use crate::util::context::key::ArrayItemKeyPath;
67

78
/// Internal error for serde errors.
89
#[derive(Debug)]
@@ -53,6 +54,17 @@ impl ConfigError {
5354
definition: definition,
5455
}
5556
}
57+
58+
pub(super) fn with_array_item_key_context(
59+
self,
60+
key: &ArrayItemKeyPath,
61+
definition: Option<Definition>,
62+
) -> ConfigError {
63+
ConfigError {
64+
error: anyhow::Error::from(self).context(format!("failed to parse config at `{key}`")),
65+
definition,
66+
}
67+
}
5668
}
5769

5870
impl std::error::Error for ConfigError {

0 commit comments

Comments
 (0)