1use std::str::FromStr;
15
16use ranvier_core::Bus;
17use serde::Serialize;
18
19use crate::ingress::{PathParams, QueryParams};
20
21pub trait BusHttpExt {
35 fn path_param<T: FromStr>(&self, name: &str) -> Result<T, String>;
54
55 fn query_param<T: FromStr>(&self, name: &str) -> Option<T>;
67
68 fn query_param_or<T: FromStr>(&self, name: &str, default: T) -> T;
79}
80
81impl BusHttpExt for Bus {
82 fn path_param<T: FromStr>(&self, name: &str) -> Result<T, String> {
83 self.read::<PathParams>()
84 .and_then(|p| p.get_parsed::<T>(name))
85 .ok_or_else(|| format!("Missing or invalid path parameter: {name}"))
86 }
87
88 fn query_param<T: FromStr>(&self, name: &str) -> Option<T> {
89 self.read::<QueryParams>()
90 .and_then(|q| q.get_parsed::<T>(name))
91 }
92
93 fn query_param_or<T: FromStr>(&self, name: &str, default: T) -> T {
94 self.query_param(name).unwrap_or(default)
95 }
96}
97
98pub fn json_outcome<T: Serialize>(value: &T) -> ranvier_core::Outcome<String, String> {
118 match serde_json::to_string(value) {
119 Ok(json) => ranvier_core::Outcome::Next(json),
120 Err(e) => ranvier_core::Outcome::Fault(format!("JSON serialization failed: {e}")),
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use ranvier_core::Bus;
128 use std::collections::HashMap;
129
130 #[test]
131 fn path_param_parses_uuid() {
132 let mut bus = Bus::new();
133 let mut values = HashMap::new();
134 values.insert("id".to_string(), "550e8400-e29b-41d4-a716-446655440000".to_string());
135 bus.insert(PathParams::new(values));
136
137 let id: uuid::Uuid = bus.path_param("id").unwrap();
138 assert_eq!(id.to_string(), "550e8400-e29b-41d4-a716-446655440000");
139 }
140
141 #[test]
142 fn path_param_parses_i64() {
143 let mut bus = Bus::new();
144 let mut values = HashMap::new();
145 values.insert("page".to_string(), "42".to_string());
146 bus.insert(PathParams::new(values));
147
148 let page: i64 = bus.path_param("page").unwrap();
149 assert_eq!(page, 42);
150 }
151
152 #[test]
153 fn path_param_missing_returns_err() {
154 let bus = Bus::new();
155 let result: Result<i64, String> = bus.path_param("id");
156 assert!(result.is_err());
157 assert!(result.unwrap_err().contains("Missing or invalid"));
158 }
159
160 #[test]
161 fn path_param_invalid_parse_returns_err() {
162 let mut bus = Bus::new();
163 let mut values = HashMap::new();
164 values.insert("id".to_string(), "not-a-uuid".to_string());
165 bus.insert(PathParams::new(values));
166
167 let result: Result<uuid::Uuid, String> = bus.path_param("id");
168 assert!(result.is_err());
169 }
170
171 #[test]
172 fn query_param_parses_value() {
173 let mut bus = Bus::new();
174 bus.insert(QueryParams::from_query("page=5&limit=20"));
175
176 let page: Option<i64> = bus.query_param("page");
177 assert_eq!(page, Some(5));
178
179 let limit: Option<i64> = bus.query_param("limit");
180 assert_eq!(limit, Some(20));
181 }
182
183 #[test]
184 fn query_param_missing_returns_none() {
185 let mut bus = Bus::new();
186 bus.insert(QueryParams::from_query("page=1"));
187
188 let missing: Option<i64> = bus.query_param("nonexistent");
189 assert!(missing.is_none());
190 }
191
192 #[test]
193 fn query_param_no_query_params_in_bus_returns_none() {
194 let bus = Bus::new();
195 let result: Option<i64> = bus.query_param("page");
196 assert!(result.is_none());
197 }
198
199 #[test]
200 fn query_param_or_returns_parsed_value() {
201 let mut bus = Bus::new();
202 bus.insert(QueryParams::from_query("page=3"));
203
204 let page: i64 = bus.query_param_or("page", 1);
205 assert_eq!(page, 3);
206 }
207
208 #[test]
209 fn query_param_or_returns_default_when_missing() {
210 let mut bus = Bus::new();
211 bus.insert(QueryParams::from_query(""));
212
213 let page: i64 = bus.query_param_or("page", 1);
214 assert_eq!(page, 1);
215
216 let per_page: i64 = bus.query_param_or("per_page", 20);
217 assert_eq!(per_page, 20);
218 }
219
220 #[test]
221 fn json_outcome_success() {
222 #[derive(Serialize)]
223 struct Resp {
224 status: String,
225 }
226 let resp = Resp {
227 status: "ok".into(),
228 };
229 let outcome = json_outcome(&resp);
230 assert!(outcome.is_next());
231 match outcome {
232 ranvier_core::Outcome::Next(json) => {
233 assert!(json.contains("\"status\""));
234 assert!(json.contains("\"ok\""));
235 }
236 _ => panic!("Expected Next"),
237 }
238 }
239
240 #[test]
241 fn json_outcome_with_vec() {
242 let items = vec![1, 2, 3];
243 let outcome = json_outcome(&items);
244 assert!(outcome.is_next());
245 match outcome {
246 ranvier_core::Outcome::Next(json) => {
247 assert_eq!(json, "[1,2,3]");
248 }
249 _ => panic!("Expected Next"),
250 }
251 }
252}