1use std::collections::HashMap;
2
3use crate::{ResponsePath, ResponseValue};
4
5#[derive(Debug, thiserror::Error)]
7pub enum ResponseError {
8 #[error("Missing response for path: {0}")]
9 MissingPath(ResponsePath),
10
11 #[error("Type mismatch at path '{path}': expected {expected}, got {actual}")]
12 TypeMismatch {
13 path: ResponsePath,
14 expected: &'static str,
15 actual: &'static str,
16 },
17}
18
19#[derive(Debug, Clone, Default)]
25pub struct Responses {
26 values: HashMap<ResponsePath, ResponseValue>,
27}
28
29impl Responses {
30 pub fn new() -> Self {
32 Self {
33 values: HashMap::new(),
34 }
35 }
36
37 pub fn insert(&mut self, path: impl Into<ResponsePath>, value: impl Into<ResponseValue>) {
39 self.values.insert(path.into(), value.into());
40 }
41
42 pub fn get(&self, path: &ResponsePath) -> Option<&ResponseValue> {
44 self.values.get(path)
45 }
46
47 pub fn contains(&self, path: &ResponsePath) -> bool {
49 self.values.contains_key(path)
50 }
51
52 pub fn remove(&mut self, path: &ResponsePath) -> Option<ResponseValue> {
54 self.values.remove(path)
55 }
56
57 pub fn iter(&self) -> impl Iterator<Item = (&ResponsePath, &ResponseValue)> {
59 self.values.iter()
60 }
61
62 pub fn len(&self) -> usize {
64 self.values.len()
65 }
66
67 pub fn is_empty(&self) -> bool {
69 self.values.is_empty()
70 }
71
72 pub fn extend(&mut self, other: Responses) {
74 self.values.extend(other.values);
75 }
76
77 pub fn filter_prefix(&self, prefix: &ResponsePath) -> Self {
97 let mut filtered = Responses::new();
98 for (path, value) in &self.values {
99 if let Some(stripped) = path.strip_path_prefix(prefix) {
100 filtered.values.insert(stripped, value.clone());
101 }
102 }
103 filtered
104 }
105
106 pub fn get_string(&self, path: &ResponsePath) -> Result<&str, ResponseError> {
110 match self.get(path) {
111 Some(ResponseValue::String(s)) => Ok(s),
112 Some(other) => Err(ResponseError::TypeMismatch {
113 path: path.clone(),
114 expected: "String",
115 actual: other.type_name(),
116 }),
117 None => Err(ResponseError::MissingPath(path.clone())),
118 }
119 }
120
121 pub fn get_int(&self, path: &ResponsePath) -> Result<i64, ResponseError> {
123 match self.get(path) {
124 Some(ResponseValue::Int(i)) => Ok(*i),
125 Some(other) => Err(ResponseError::TypeMismatch {
126 path: path.clone(),
127 expected: "Int",
128 actual: other.type_name(),
129 }),
130 None => Err(ResponseError::MissingPath(path.clone())),
131 }
132 }
133
134 pub fn get_float(&self, path: &ResponsePath) -> Result<f64, ResponseError> {
136 match self.get(path) {
137 Some(ResponseValue::Float(f)) => Ok(*f),
138 Some(other) => Err(ResponseError::TypeMismatch {
139 path: path.clone(),
140 expected: "Float",
141 actual: other.type_name(),
142 }),
143 None => Err(ResponseError::MissingPath(path.clone())),
144 }
145 }
146
147 pub fn get_bool(&self, path: &ResponsePath) -> Result<bool, ResponseError> {
149 match self.get(path) {
150 Some(ResponseValue::Bool(b)) => Ok(*b),
151 Some(other) => Err(ResponseError::TypeMismatch {
152 path: path.clone(),
153 expected: "Bool",
154 actual: other.type_name(),
155 }),
156 None => Err(ResponseError::MissingPath(path.clone())),
157 }
158 }
159
160 pub fn get_chosen_variant(&self, path: &ResponsePath) -> Result<usize, ResponseError> {
162 match self.get(path) {
163 Some(ResponseValue::ChosenVariant(idx)) => Ok(*idx),
164 Some(other) => Err(ResponseError::TypeMismatch {
165 path: path.clone(),
166 expected: "ChosenVariant",
167 actual: other.type_name(),
168 }),
169 None => Err(ResponseError::MissingPath(path.clone())),
170 }
171 }
172
173 pub fn get_chosen_variants(&self, path: &ResponsePath) -> Result<&[usize], ResponseError> {
175 match self.get(path) {
176 Some(ResponseValue::ChosenVariants(indices)) => Ok(indices),
177 Some(other) => Err(ResponseError::TypeMismatch {
178 path: path.clone(),
179 expected: "ChosenVariants",
180 actual: other.type_name(),
181 }),
182 None => Err(ResponseError::MissingPath(path.clone())),
183 }
184 }
185
186 pub fn get_string_list(&self, path: &ResponsePath) -> Result<&[String], ResponseError> {
188 match self.get(path) {
189 Some(ResponseValue::StringList(list)) => Ok(list),
190 Some(other) => Err(ResponseError::TypeMismatch {
191 path: path.clone(),
192 expected: "StringList",
193 actual: other.type_name(),
194 }),
195 None => Err(ResponseError::MissingPath(path.clone())),
196 }
197 }
198
199 pub fn get_int_list(&self, path: &ResponsePath) -> Result<&[i64], ResponseError> {
201 match self.get(path) {
202 Some(ResponseValue::IntList(list)) => Ok(list),
203 Some(other) => Err(ResponseError::TypeMismatch {
204 path: path.clone(),
205 expected: "IntList",
206 actual: other.type_name(),
207 }),
208 None => Err(ResponseError::MissingPath(path.clone())),
209 }
210 }
211
212 pub fn get_float_list(&self, path: &ResponsePath) -> Result<&[f64], ResponseError> {
214 match self.get(path) {
215 Some(ResponseValue::FloatList(list)) => Ok(list),
216 Some(other) => Err(ResponseError::TypeMismatch {
217 path: path.clone(),
218 expected: "FloatList",
219 actual: other.type_name(),
220 }),
221 None => Err(ResponseError::MissingPath(path.clone())),
222 }
223 }
224
225 pub fn has_value(&self, path: &ResponsePath) -> bool {
230 match self.get(path) {
231 Some(ResponseValue::String(s)) => !s.is_empty(),
232 Some(_) => true,
233 None => false,
234 }
235 }
236}
237
238impl IntoIterator for Responses {
239 type Item = (ResponsePath, ResponseValue);
240 type IntoIter = std::collections::hash_map::IntoIter<ResponsePath, ResponseValue>;
241
242 fn into_iter(self) -> Self::IntoIter {
243 self.values.into_iter()
244 }
245}
246
247impl<'a> IntoIterator for &'a Responses {
248 type Item = (&'a ResponsePath, &'a ResponseValue);
249 type IntoIter = std::collections::hash_map::Iter<'a, ResponsePath, ResponseValue>;
250
251 fn into_iter(self) -> Self::IntoIter {
252 self.values.iter()
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn insert_and_get() {
262 let mut responses = Responses::new();
263 responses.insert("name", "Alice");
264 responses.insert("age", ResponseValue::Int(30));
265
266 assert_eq!(
267 responses.get_string(&ResponsePath::new("name")).unwrap(),
268 "Alice"
269 );
270 assert_eq!(responses.get_int(&ResponsePath::new("age")).unwrap(), 30);
271 }
272
273 #[test]
274 fn filter_prefix() {
275 let mut responses = Responses::new();
276 responses.insert("address.street", "123 Main St");
277 responses.insert("address.city", "Springfield");
278 responses.insert("name", "Alice");
279
280 let filtered = responses.filter_prefix(&ResponsePath::new("address"));
281 assert_eq!(filtered.len(), 2);
282 assert_eq!(
283 filtered.get_string(&ResponsePath::new("street")).unwrap(),
284 "123 Main St"
285 );
286 assert_eq!(
287 filtered.get_string(&ResponsePath::new("city")).unwrap(),
288 "Springfield"
289 );
290 }
291
292 #[test]
293 fn type_mismatch_error() {
294 let mut responses = Responses::new();
295 responses.insert("age", ResponseValue::Int(30));
296
297 let result = responses.get_string(&ResponsePath::new("age"));
298 assert!(matches!(result, Err(ResponseError::TypeMismatch { .. })));
299 }
300}