Skip to content

Commit fd13826

Browse files
committed
Remove anyhow
Signed-off-by: Javier G. Sogo <[email protected]>
1 parent b2afcf8 commit fd13826

File tree

3 files changed

+116
-93
lines changed

3 files changed

+116
-93
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ tungstenite = { version = "0.24.0", features = ["native-tls"] }
1515
url = "2.5.2"
1616
http = "1.1.0"
1717
thiserror = "2.0.4"
18-
anyhow = "1.0.94"
1918

2019
[dev-dependencies]
2120
dotenvy = "0.15.7"

src/errors.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
use std::sync::PoisonError;
1+
use std::{str::ParseBoolError, sync::PoisonError};
22

33
use thiserror::Error;
44

5+
use crate::{
6+
models::{Segment, SegmentRule},
7+
AttrValue,
8+
};
9+
510
pub type Result<T> = std::result::Result<T, Error>;
611

712
#[derive(Debug, Error)]
@@ -42,12 +47,22 @@ pub enum Error {
4247
ConfigurationAccessError(#[from] ConfigurationAccessError),
4348

4449
#[error("Failed to evaluate entity: {0}")]
45-
EntityEvaluationError(String),
50+
EntityEvaluationError(EntityEvaluationError),
4651

4752
#[error("{0}")]
4853
Other(String),
4954
}
5055

56+
#[derive(Debug, Error)]
57+
#[error("sdaf '{0}'")]
58+
pub struct EntityEvaluationError(pub(crate) SegmentEvaluationError);
59+
60+
impl From<SegmentEvaluationError> for Error {
61+
fn from(value: SegmentEvaluationError) -> Self {
62+
Self::EntityEvaluationError(EntityEvaluationError(value))
63+
}
64+
}
65+
5166
impl<T> From<PoisonError<T>> for Error {
5267
fn from(_value: PoisonError<T>) -> Self {
5368
Error::CannotAcquireLock
@@ -94,3 +109,52 @@ impl<T> From<PoisonError<T>> for ConfigurationAccessError {
94109
ConfigurationAccessError::LockAcquisitionError
95110
}
96111
}
112+
113+
#[derive(Debug, Error)]
114+
pub(crate) enum CheckOperatorErrorDetail {
115+
#[error("Entity attribute is not a string.")]
116+
StringExpected,
117+
118+
#[error("Entity attribute has unexpected type: Boolean.")]
119+
BooleanExpected(#[from] std::str::ParseBoolError),
120+
121+
#[error("Entity attribute has unexpected type: Number.")]
122+
NumberExpected(#[from] std::num::ParseFloatError),
123+
124+
#[error("Entity attribute is not a number.")]
125+
EntityAttrNotANumber,
126+
127+
#[error("Operator not implemented.")]
128+
OperatorNotImplemented,
129+
}
130+
131+
#[derive(Debug, Error)]
132+
pub(crate) enum SegmentEvaluationError {
133+
#[error("Operation")]
134+
SegmentEvaluationFailed(#[from] SegmentEvaluationErrorKind),
135+
136+
#[error("Segment ID '{0}' not found")]
137+
SegmentIdNotFound(String),
138+
}
139+
#[derive(Debug, Error)]
140+
#[error("Operation")]
141+
pub(crate) struct SegmentEvaluationErrorKind {
142+
pub(crate) segment: Segment,
143+
pub(crate) segment_rule: SegmentRule,
144+
pub(crate) attr_value: AttrValue,
145+
pub(crate) source: CheckOperatorErrorDetail,
146+
}
147+
148+
impl From<(CheckOperatorErrorDetail, &Segment, &SegmentRule, &AttrValue)>
149+
for SegmentEvaluationError
150+
{
151+
fn from(value: (CheckOperatorErrorDetail, &Segment, &SegmentRule, &AttrValue)) -> Self {
152+
let (source, segment, segment_rule, attr_value) = value;
153+
Self::SegmentEvaluationFailed(SegmentEvaluationErrorKind {
154+
segment: segment.clone(),
155+
segment_rule: segment_rule.clone(),
156+
attr_value: attr_value.clone(),
157+
source,
158+
})
159+
}
160+
}

src/segment_evaluation.rs

Lines changed: 50 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,13 @@
1414

1515
use std::collections::HashMap;
1616

17-
use crate::errors::{Error, Result};
17+
use crate::errors::{CheckOperatorErrorDetail, Result, SegmentEvaluationError};
1818
use crate::models::Segment;
1919
use crate::{
2020
entity::{AttrValue, Entity},
2121
models::TargetingRule,
2222
};
2323

24-
// For chaining errors creating useful error messages:
25-
use anyhow::anyhow;
26-
use anyhow::{Context, Result as AnyhowResult};
27-
2824
pub(crate) fn find_applicable_segment_rule_for_entity(
2925
segments: &HashMap<String, Segment>,
3026
segment_rules: impl Iterator<Item = TargetingRule>,
@@ -34,26 +30,18 @@ pub(crate) fn find_applicable_segment_rule_for_entity(
3430
targeting_rules.sort_by(|a, b| a.order.cmp(&b.order));
3531

3632
for targeting_rule in targeting_rules.into_iter() {
37-
if targeting_rule_applies_to_entity(segments, &targeting_rule, entity).map_err(|e| {
38-
// This terminates the use of anyhow in this module, converting all errors:
39-
let cause: String = e.chain().map(|c| format!("\nCaused by: {c}")).collect();
40-
Error::EntityEvaluationError(format!(
41-
"Failed to evaluate entity '{}' against targeting rule '{}'.{cause}",
42-
entity.get_id(),
43-
targeting_rule.order
44-
))
45-
})? {
33+
if targeting_rule_applies_to_entity(segments, &targeting_rule, entity)? {
4634
return Ok(Some(targeting_rule));
4735
}
4836
}
49-
return Ok(None);
37+
Ok(None)
5038
}
5139

5240
fn targeting_rule_applies_to_entity(
5341
segments: &HashMap<String, Segment>,
5442
targeting_rule: &TargetingRule,
5543
entity: &impl Entity,
56-
) -> AnyhowResult<bool> {
44+
) -> std::result::Result<bool, SegmentEvaluationError> {
5745
// TODO: we need to get the naming correct here to distinguish between rules, segments, segment_ids, targeting_rules etc. correctly
5846
let rules = &targeting_rule.rules;
5947
for rule in rules.iter() {
@@ -69,21 +57,25 @@ fn segment_applies_to_entity(
6957
segments: &HashMap<String, Segment>,
7058
segment_ids: &[String],
7159
entity: &impl Entity,
72-
) -> AnyhowResult<bool> {
60+
) -> std::result::Result<bool, SegmentEvaluationError> {
7361
for segment_id in segment_ids.iter() {
74-
let segment = segments.get(segment_id).ok_or(Error::Other(
75-
format!("Segment '{segment_id}' not found.").into(),
76-
))?;
77-
let applies = belong_to_segment(segment, entity.get_attributes())
78-
.context(format!("Failed to evaluate segment '{segment_id}'"))?;
62+
let segment = segments
63+
.get(segment_id)
64+
.ok_or(SegmentEvaluationError::SegmentIdNotFound(
65+
segment_id.clone(),
66+
))?;
67+
let applies = belong_to_segment(segment, entity.get_attributes())?;
7968
if applies {
8069
return Ok(true);
8170
}
8271
}
8372
Ok(false)
8473
}
8574

86-
fn belong_to_segment(segment: &Segment, attrs: HashMap<String, AttrValue>) -> AnyhowResult<bool> {
75+
fn belong_to_segment(
76+
segment: &Segment,
77+
attrs: HashMap<String, AttrValue>,
78+
) -> std::result::Result<bool, SegmentEvaluationError> {
8779
for rule in segment.rules.iter() {
8880
let operator = &rule.operator;
8981
let attr_name = &rule.attribute_name;
@@ -94,31 +86,26 @@ fn belong_to_segment(segment: &Segment, attrs: HashMap<String, AttrValue>) -> An
9486
let rule_result = match attr_value {
9587
None => {
9688
println!("Warning: Operation '{attr_name}' '{operator}' '[...]' failed to evaluate: '{attr_name}' not found in entity");
97-
Ok(false)
89+
false
9890
}
9991
Some(attr_value) => {
10092
// FIXME: the following algorithm is too hard to read. Is it just me or do we need to simplify this?
10193
// One of the values needs to match.
10294
// Find a candidate (a candidate corresponds to a value which matches or which might match but the operator failed):
103-
let candidate = rule.values.iter().find_map(|value| {
104-
let result_for_value =
105-
check_operator(attr_value, operator, value).context(format!(
106-
"Operation '{attr_name}' '{operator}' '{value}' failed to evaluate."
107-
));
108-
match result_for_value {
95+
let candidate = rule
96+
.values
97+
.iter()
98+
.find_map(|value| match check_operator(attr_value, operator, value) {
10999
Ok(true) => Some(Ok(())),
110100
Ok(false) => None,
111101
Err(e) => Some(Err(e)),
112-
}
113-
});
102+
})
103+
.transpose()
104+
.map_err(|e| (e, segment, rule, attr_value))?;
114105
// check if the candidate is good, or if the operator failed:
115-
match candidate {
116-
None => Ok(false),
117-
Some(Ok(())) => Ok(true),
118-
Some(Err(e)) => Err(e),
119-
}
106+
candidate.is_some()
120107
}
121-
}?;
108+
};
122109
// All rules must match:
123110
if !rule_result {
124111
return Ok(false);
@@ -131,83 +118,47 @@ fn check_operator(
131118
attribute_value: &AttrValue,
132119
operator: &str,
133120
reference_value: &str,
134-
) -> AnyhowResult<bool> {
121+
) -> std::result::Result<bool, CheckOperatorErrorDetail> {
135122
match operator {
136123
"is" => match attribute_value {
137124
AttrValue::String(data) => Ok(*data == reference_value),
138-
AttrValue::Boolean(data) => {
139-
let result = *data
140-
== reference_value
141-
.parse::<bool>()
142-
.map_err(|_| anyhow!("Entity attribute has unexpected type: Boolean."))?;
143-
Ok(result)
144-
}
145-
AttrValue::Numeric(data) => {
146-
let result = *data
147-
== reference_value
148-
.parse::<f64>()
149-
.map_err(|_| anyhow!("Entity attribute has unexpected type: Number."))?;
150-
Ok(result)
151-
}
125+
AttrValue::Boolean(data) => Ok(*data == reference_value.parse::<bool>()?),
126+
AttrValue::Numeric(data) => Ok(*data == reference_value.parse::<f64>()?),
152127
},
153128
"contains" => match attribute_value {
154129
AttrValue::String(data) => Ok(data.contains(reference_value)),
155-
_ => Err(anyhow!("Entity attribute is not a string.")),
130+
_ => Err(CheckOperatorErrorDetail::StringExpected),
156131
},
157132
"startsWith" => match attribute_value {
158133
AttrValue::String(data) => Ok(data.starts_with(reference_value)),
159-
_ => Err(anyhow!("Entity attribute is not a string.")),
134+
_ => Err(CheckOperatorErrorDetail::StringExpected),
160135
},
161136
"endsWith" => match attribute_value {
162137
AttrValue::String(data) => Ok(data.ends_with(reference_value)),
163-
_ => Err(anyhow!("Entity attribute is not a string.")),
138+
_ => Err(CheckOperatorErrorDetail::StringExpected),
164139
},
165140
"greaterThan" => match attribute_value {
166141
// TODO: Go implementation also compares strings (by parsing them as floats). Do we need this?
167142
// https://github.com/IBM/appconfiguration-go-sdk/blob/master/lib/internal/models/Rule.go#L82
168143
// TODO: we could have numbers not representable as f64, maybe we should try to parse it to i64 and u64 too?
169144
// TODO: we should have a different nesting style here: match the reference_value first and error out when given
170145
// entity attr does not match. This would yield more natural error messages
171-
AttrValue::Numeric(data) => {
172-
let result = *data
173-
> reference_value
174-
.parse()
175-
.map_err(|_| Error::Other("Value cannot convert into f64.".into()))?;
176-
Ok(result)
177-
}
178-
_ => Err(anyhow!("Entity attribute is not a number.")),
146+
AttrValue::Numeric(data) => Ok(*data > reference_value.parse()?),
147+
_ => Err(CheckOperatorErrorDetail::EntityAttrNotANumber),
179148
},
180149
"lesserThan" => match attribute_value {
181-
AttrValue::Numeric(data) => {
182-
let result = *data
183-
< reference_value
184-
.parse()
185-
.map_err(|_| Error::Other("Value cannot convert into f64.".into()))?;
186-
Ok(result)
187-
}
188-
_ => Err(anyhow!("Entity attribute is not a number.")),
150+
AttrValue::Numeric(data) => Ok(*data < reference_value.parse()?),
151+
_ => Err(CheckOperatorErrorDetail::EntityAttrNotANumber),
189152
},
190153
"greaterThanEquals" => match attribute_value {
191-
AttrValue::Numeric(data) => {
192-
let result = *data
193-
>= reference_value
194-
.parse()
195-
.map_err(|_| Error::Other("Value cannot convert into f64.".into()))?;
196-
Ok(result)
197-
}
198-
_ => Err(anyhow!("Entity attribute is not a number.")),
154+
AttrValue::Numeric(data) => Ok(*data >= reference_value.parse()?),
155+
_ => Err(CheckOperatorErrorDetail::EntityAttrNotANumber),
199156
},
200157
"lesserThanEquals" => match attribute_value {
201-
AttrValue::Numeric(data) => {
202-
let result = *data
203-
<= reference_value
204-
.parse()
205-
.map_err(|_| Error::Other("Value cannot convert into f64.".into()))?;
206-
Ok(result)
207-
}
208-
_ => Err(anyhow!("Entity attribute is not a number.")),
158+
AttrValue::Numeric(data) => Ok(*data <= reference_value.parse()?),
159+
_ => Err(CheckOperatorErrorDetail::EntityAttrNotANumber),
209160
},
210-
_ => Err(anyhow!("Operator not implemented")),
161+
_ => Err(CheckOperatorErrorDetail::OperatorNotImplemented),
211162
}
212163
}
213164

@@ -218,6 +169,7 @@ pub mod tests {
218169
models::{ConfigValue, Segment, SegmentRule, Segments, TargetingRule},
219170
AttrValue,
220171
};
172+
use crate::errors::Error;
221173
use rstest::*;
222174

223175
#[fixture]
@@ -316,7 +268,15 @@ pub mod tests {
316268
// Caused by: Operation 'name' 'is' 'heinz' failed to evaluate.
317269
// Caused by: Entity attribute has unexpected type: Number.
318270
// We are checking here that the parts are present to allow debugging of config by the user:
319-
let msg = rule.unwrap_err().to_string();
271+
272+
let e = rule.unwrap_err();
273+
assert!(matches!(
274+
e,
275+
Error::EntityEvaluationError(ref v)
276+
));
277+
278+
let msg = e.to_string();
279+
assert_eq!(msg, "lol");
320280
assert!(msg.contains("'a2'"));
321281
assert!(msg.contains("'0'"));
322282
assert!(msg.contains("'some_segment_id_1'"));

0 commit comments

Comments
 (0)