1use std::collections::HashMap;
2use serde_json::Value;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum JsonPathError {
7 #[error("{message}\nPath: {path}\nActual Value: {actual}\n{}", context_string(.context, .expected))]
8 AssertionFailed {
9 message: String,
10 path: String,
11 actual: Value,
12 expected: Option<Value>,
13 context: HashMap<String, String>,
14 },
15
16 #[error("Invalid JSONPath expression: {0}")]
17 InvalidPath(String),
18}
19
20fn context_string(context: &HashMap<String, String>, expected: &Option<Value>) -> String {
22 let mut parts = Vec::new();
23
24 if let Some(exp) = expected {
26 parts.push(format!("Expected Value: {}", exp));
27 }
28
29 for (key, value) in context {
31 parts.push(format!("{}: {}", key, value));
32 }
33
34 parts.join("\n")
35}
36
37impl JsonPathError {
38 pub fn assertion_failed(
39 message: impl Into<String>,
40 path: impl Into<String>,
41 actual: Value,
42 expected: Option<Value>,
43 context: HashMap<String, String>,
44 ) -> Self {
45 JsonPathError::AssertionFailed {
46 message: message.into(),
47 path: path.into(),
48 actual,
49 expected,
50 context,
51 }
52 }
53
54 pub fn type_mismatch(path: String, actual: Value, expected_type: &str) -> Self {
55 let mut context = HashMap::new();
56 context.insert("Expected Type".to_string(), expected_type.to_string());
57 context.insert("Actual Type".to_string(), type_name(&actual));
58
59 JsonPathError::AssertionFailed {
60 message: format!("Expected value of type {}", expected_type),
61 path,
62 actual,
63 expected: None,
64 context,
65 }
66 }
67
68 pub fn value_mismatch(path: String, actual: Value, expected: Value) -> Self {
69 let mut context = HashMap::new();
70 context.insert("Operation".to_string(), "Equality".to_string());
71
72 JsonPathError::AssertionFailed {
73 message: "Value mismatch".to_string(),
74 path,
75 actual,
76 expected: Some(expected),
77 context,
78 }
79 }
80
81 pub fn comparison_failed(
82 path: String,
83 actual: Value,
84 operation: &str,
85 comparison_value: Value,
86 ) -> Self {
87 let mut context = HashMap::new();
88 context.insert("Operation".to_string(), operation.to_string());
89 context.insert("Comparison Value".to_string(), comparison_value.to_string());
90
91 JsonPathError::AssertionFailed {
92 message: format!("Value comparison failed for operation: {}", operation),
93 path,
94 actual,
95 expected: None,
96 context,
97 }
98 }
99
100 pub fn property_error(
101 path: String,
102 actual: Value,
103 message: String,
104 context: HashMap<String, String>,
105 ) -> Self {
106 JsonPathError::AssertionFailed {
107 message,
108 path,
109 actual,
110 expected: None,
111 context,
112 }
113 }
114}
115
116fn type_name(value: &Value) -> String {
118 match value {
119 Value::Null => "null",
120 Value::Bool(_) => "boolean",
121 Value::Number(_) => "number",
122 Value::String(_) => "string",
123 Value::Array(_) => "array",
124 Value::Object(_) => "object",
125 }.to_string()
126}
127
128pub trait ErrorContext<T> {
130 fn with_context<K, V>(self, key: K, value: V) -> Result<T, JsonPathError>
131 where
132 K: Into<String>,
133 V: Into<String>;
134
135 fn with_contexts<I, K, V>(self, contexts: I) -> Result<T, JsonPathError>
136 where
137 I: IntoIterator<Item = (K, V)>,
138 K: Into<String>,
139 V: Into<String>;
140}
141
142impl<T> ErrorContext<T> for Result<T, JsonPathError> {
143 fn with_context<K, V>(self, key: K, value: V) -> Result<T, JsonPathError>
144 where
145 K: Into<String>,
146 V: Into<String>,
147 {
148 self.map_err(|err| {
149 if let JsonPathError::AssertionFailed {
150 message,
151 path,
152 actual,
153 expected,
154 mut context,
155 } = err
156 {
157 context.insert(key.into(), value.into());
158 JsonPathError::AssertionFailed {
159 message,
160 path,
161 actual,
162 expected,
163 context,
164 }
165 } else {
166 err
167 }
168 })
169 }
170
171 fn with_contexts<I, K, V>(self, contexts: I) -> Result<T, JsonPathError>
172 where
173 I: IntoIterator<Item = (K, V)>,
174 K: Into<String>,
175 V: Into<String>,
176 {
177 self.map_err(|err| {
178 if let JsonPathError::AssertionFailed {
179 message,
180 path,
181 actual,
182 expected,
183 mut context,
184 } = err
185 {
186 for (k, v) in contexts {
187 context.insert(k.into(), v.into());
188 }
189 JsonPathError::AssertionFailed {
190 message,
191 path,
192 actual,
193 expected,
194 context,
195 }
196 } else {
197 err
198 }
199 })
200 }
201}