1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
//! Declare an error type.
/// An error related to an option passed to Arti via a configuration
/// builder.
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum ConfigBuildError {
/// A mandatory field was not present.
#[error("Field was not provided: {field}")]
MissingField {
/// The name of the missing field.
field: String,
},
/// A single field had a value that proved to be unusable.
#[error("Value of {field} was incorrect: {problem}")]
Invalid {
/// The name of the invalid field
field: String,
/// A description of the problem.
problem: String,
},
/// Multiple fields are inconsistent.
#[error("Fields {fields:?} are inconsistent: {problem}")]
Inconsistent {
/// The names of the inconsistent fields
fields: Vec<String>,
/// The problem that makes them inconsistent
problem: String,
},
}
impl From<derive_builder::UninitializedFieldError> for ConfigBuildError {
fn from(val: derive_builder::UninitializedFieldError) -> Self {
ConfigBuildError::MissingField {
field: val.field_name().to_string(),
}
}
}
impl ConfigBuildError {
/// Return a new ConfigBuildError that prefixes its field name with
/// `prefix` and a dot.
pub fn within(&self, prefix: &str) -> Self {
use ConfigBuildError::*;
match self {
MissingField { field } => MissingField {
field: format!("{}.{}", prefix, field),
},
Invalid { field, problem } => Invalid {
field: format!("{}.{}", prefix, field),
problem: problem.clone(),
},
Inconsistent { fields, problem } => Inconsistent {
fields: fields.iter().map(|f| format!("{}.{}", prefix, f)).collect(),
problem: problem.clone(),
},
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn within() {
let e1 = ConfigBuildError::MissingField {
field: "lettuce".to_owned(),
};
let e2 = ConfigBuildError::Invalid {
field: "tomato".to_owned(),
problem: "too crunchy".to_owned(),
};
let e3 = ConfigBuildError::Inconsistent {
fields: vec!["mayo".to_owned(), "avocado".to_owned()],
problem: "pick one".to_owned(),
};
assert_eq!(
&e1.within("sandwich").to_string(),
"Field was not provided: sandwich.lettuce"
);
assert_eq!(
&e2.within("sandwich").to_string(),
"Value of sandwich.tomato was incorrect: too crunchy"
);
assert_eq!(
&e3.within("sandwich").to_string(),
r#"Fields ["sandwich.mayo", "sandwich.avocado"] are inconsistent: pick one"#
);
}
#[derive(derive_builder::Builder, Debug)]
#[builder(build_fn(error = "ConfigBuildError"))]
#[allow(dead_code)]
struct Cephalopod {
// arms have suction cups for their whole length
arms: u8,
// Tentacles have suction cups at the ends
tentacles: u8,
}
#[test]
fn build_err() {
let squid = CephalopodBuilder::default().arms(8).tentacles(2).build();
let octopus = CephalopodBuilder::default().arms(8).build();
assert!(squid.is_ok());
let squid = squid.unwrap();
assert_eq!(squid.arms, 8);
assert_eq!(squid.tentacles, 2);
assert!(octopus.is_err());
assert_eq!(
&octopus.unwrap_err().to_string(),
"Field was not provided: tentacles"
);
}
}
|