1use camel_api::CamelError;
2
3use crate::UriComponents;
4
5pub trait UriConfig: Sized {
10 fn scheme() -> &'static str;
12
13 fn from_uri(uri: &str) -> Result<Self, CamelError>;
15
16 fn from_components(parts: UriComponents) -> Result<Self, CamelError>;
18
19 fn validate(self) -> Result<Self, CamelError> {
21 Ok(self)
22 }
23}
24
25#[cfg(test)]
26mod tests {
27 use super::*;
28
29 #[test]
30 fn test_trait_exists() {
31 fn _uses_trait<T: UriConfig>() {}
33 }
34}
35
36#[cfg(test)]
37mod derive_tests {
38 use super::*;
39 use crate::UriConfig;
40
41 extern crate self as camel_endpoint;
43
44 #[derive(Debug, Clone, UriConfig)]
46 #[uri_scheme = "test"]
47 struct SimpleConfig {
48 name: String,
49 }
50
51 #[test]
52 fn test_simple_path_extraction() {
53 let config = SimpleConfig::from_uri("test:hello").unwrap();
54 assert_eq!(config.name, "hello");
55 }
56
57 #[test]
58 fn test_simple_scheme() {
59 assert_eq!(SimpleConfig::scheme(), "test");
60 }
61
62 #[derive(Debug, Clone, UriConfig)]
64 #[uri_scheme = "test"]
65 struct ConfigWithParams {
66 name: String,
67 #[uri_param(default = "1000")]
68 timeout: u64,
69 #[uri_param(default = "true")]
70 enabled: bool,
71 }
72
73 #[test]
74 fn test_params_with_defaults() {
75 let config = ConfigWithParams::from_uri("test:foo?timeout=500").unwrap();
76 assert_eq!(config.name, "foo");
77 assert_eq!(config.timeout, 500);
78 assert!(config.enabled); }
80
81 #[test]
82 fn test_params_all_specified() {
83 let config = ConfigWithParams::from_uri("test:bar?timeout=2000&enabled=false").unwrap();
84 assert_eq!(config.name, "bar");
85 assert_eq!(config.timeout, 2000);
86 assert!(!config.enabled);
87 }
88
89 #[test]
90 fn test_scheme_validation() {
91 let result = SimpleConfig::from_uri("wrong:hello");
92 assert!(result.is_err());
93 if let Err(CamelError::InvalidUri(msg)) = result {
94 assert!(msg.contains("expected scheme 'test'"));
95 assert!(msg.contains("got 'wrong'"));
96 } else {
97 panic!("Expected InvalidUri error");
98 }
99 }
100
101 #[derive(Debug, Clone, UriConfig)]
103 #[uri_scheme = "timer"]
104 struct TimerConfig {
105 timer_name: String,
106 #[uri_param]
107 period: Option<u64>,
108 #[uri_param]
109 repeat: Option<bool>,
110 #[uri_param]
111 description: Option<String>,
112 }
113
114 #[test]
115 fn test_option_params_present() {
116 let config =
117 TimerConfig::from_uri("timer:tick?period=1000&repeat=true&description=hello").unwrap();
118 assert_eq!(config.timer_name, "tick");
119 assert_eq!(config.period, Some(1000));
120 assert_eq!(config.repeat, Some(true));
121 assert_eq!(config.description, Some("hello".to_string()));
122 }
123
124 #[test]
125 fn test_option_params_absent() {
126 let config = TimerConfig::from_uri("timer:tick").unwrap();
127 assert_eq!(config.timer_name, "tick");
128 assert_eq!(config.period, None);
129 assert_eq!(config.repeat, None);
130 assert_eq!(config.description, None);
131 }
132
133 #[derive(Debug, Clone, UriConfig)]
135 #[uri_scheme = "http"]
136 struct HttpConfig {
137 url: String,
138 #[uri_param(name = "httpMethod")]
139 method: Option<String>,
140 #[uri_param(name = "connectTimeout", default = "5000")]
141 timeout_ms: u64,
142 }
143
144 #[test]
145 fn test_custom_param_names() {
146 let config =
147 HttpConfig::from_uri("http://example.com?httpMethod=POST&connectTimeout=10000")
148 .unwrap();
149 assert_eq!(config.url, "//example.com");
150 assert_eq!(config.method, Some("POST".to_string()));
151 assert_eq!(config.timeout_ms, 10000);
152 }
153
154 #[test]
155 fn test_custom_param_name_default() {
156 let config = HttpConfig::from_uri("http://example.com").unwrap();
157 assert_eq!(config.url, "//example.com");
158 assert_eq!(config.method, None);
159 assert_eq!(config.timeout_ms, 5000); }
161
162 #[derive(Debug, Clone, UriConfig)]
164 #[uri_scheme = "data"]
165 struct NumericConfig {
166 path: String,
167 #[uri_param(default = "100")]
168 count_u32: u32,
169 #[uri_param(default = "1000")]
170 count_u64: u64,
171 #[uri_param(default = "10")]
172 count_usize: usize,
173 #[uri_param(default = "-5")]
174 offset_i32: i32,
175 }
176
177 #[test]
178 fn test_numeric_types() {
179 let config = NumericConfig::from_uri(
180 "data:test?count_u32=50&count_u64=500&count_usize=5&offset_i32=-10",
181 )
182 .unwrap();
183 assert_eq!(config.path, "test");
184 assert_eq!(config.count_u32, 50);
185 assert_eq!(config.count_u64, 500);
186 assert_eq!(config.count_usize, 5);
187 assert_eq!(config.offset_i32, -10);
188 }
189
190 #[test]
191 fn test_numeric_defaults() {
192 let config = NumericConfig::from_uri("data:test").unwrap();
193 assert_eq!(config.count_u32, 100);
194 assert_eq!(config.count_u64, 1000);
195 assert_eq!(config.count_usize, 10);
196 assert_eq!(config.offset_i32, -5);
197 }
198
199 #[test]
200 fn test_invalid_numeric_value() {
201 let result = NumericConfig::from_uri("data:test?count_u32=abc");
202 assert!(result.is_err());
203 }
204
205 #[test]
207 fn test_from_components() {
208 let components = UriComponents {
209 scheme: "test".to_string(),
210 path: "hello".to_string(),
211 params: std::collections::HashMap::from([
212 ("timeout".to_string(), "500".to_string()),
213 ("enabled".to_string(), "false".to_string()),
214 ]),
215 };
216
217 let config = ConfigWithParams::from_components(components).unwrap();
218 assert_eq!(config.name, "hello");
219 assert_eq!(config.timeout, 500);
220 assert!(!config.enabled);
221 }
222
223 #[test]
225 fn test_validate_passthrough() {
226 let config = SimpleConfig::from_uri("test:hello")
227 .unwrap()
228 .validate()
229 .unwrap();
230 assert_eq!(config.name, "hello");
231 }
232
233 #[derive(Debug, Clone, UriConfig)]
235 #[uri_scheme = "feature"]
236 struct FeatureConfig {
237 feature_name: String,
238 #[uri_param]
239 enabled: bool, }
241
242 #[test]
243 fn test_bool_without_default_missing_errors() {
244 let result = FeatureConfig::from_uri("feature:test");
246 assert!(result.is_err());
247 if let Err(CamelError::InvalidUri(msg)) = result {
248 assert!(
249 msg.contains("missing required parameter"),
250 "Error should mention missing parameter, got: {}",
251 msg
252 );
253 assert!(msg.contains("enabled"), "Error should mention 'enabled'");
254 } else {
255 panic!("Expected InvalidUri error for missing bool parameter");
256 }
257 }
258
259 #[test]
260 fn test_bool_without_default_provided_works() {
261 let config = FeatureConfig::from_uri("feature:test?enabled=true").unwrap();
262 assert_eq!(config.feature_name, "test");
263 assert!(config.enabled);
264 let config = FeatureConfig::from_uri("feature:test?enabled=false").unwrap();
265 assert_eq!(config.feature_name, "test");
266 assert!(!config.enabled);
267 }
268
269 #[test]
271 fn test_option_numeric_invalid_returns_none() {
272 let config = TimerConfig::from_uri("timer:tick?period=invalid").unwrap();
274 assert_eq!(
275 config.period, None,
276 "Invalid numeric value should return None, not Some(0)"
277 );
278 }
279
280 #[derive(Debug, Clone, PartialEq, Eq)]
282 enum TestEnum {
283 Alpha,
284 Beta,
285 }
286
287 impl std::str::FromStr for TestEnum {
288 type Err = String;
289
290 fn from_str(s: &str) -> Result<Self, Self::Err> {
291 match s {
292 "alpha" => Ok(TestEnum::Alpha),
293 "beta" => Ok(TestEnum::Beta),
294 _ => Err(format!("unknown variant: {}", s)),
295 }
296 }
297 }
298
299 #[derive(Debug, Clone, UriConfig)]
300 #[uri_scheme = "enumtest"]
301 struct EnumConfig {
302 path: String,
303 #[uri_param]
304 mode: TestEnum,
305 }
306
307 #[test]
308 fn test_enum_invalid_value_includes_error() {
309 let result = EnumConfig::from_uri("enumtest:foo?mode=invalid");
310 assert!(result.is_err());
311 if let Err(CamelError::InvalidUri(msg)) = result {
312 assert!(
313 msg.contains("invalid value"),
314 "Error should mention invalid value, got: {}",
315 msg
316 );
317 assert!(
319 msg.contains("unknown variant"),
320 "Error should include parse error details, got: {}",
321 msg
322 );
323 assert!(
324 msg.contains("invalid"),
325 "Error should include the invalid value, got: {}",
326 msg
327 );
328 } else {
329 panic!("Expected InvalidUri error for invalid enum value");
330 }
331 }
332
333 #[test]
334 fn test_enum_valid_value_works() {
335 let config = EnumConfig::from_uri("enumtest:foo?mode=alpha").unwrap();
336 assert_eq!(config.path, "foo");
337 assert_eq!(config.mode, TestEnum::Alpha);
338 let config = EnumConfig::from_uri("enumtest:foo?mode=beta").unwrap();
339 assert_eq!(config.path, "foo");
340 assert_eq!(config.mode, TestEnum::Beta);
341 }
342
343 #[derive(Debug, Clone, UriConfig)]
345 #[uri_scheme = "timer"]
346 struct TimerTestConfig {
347 name: String,
348
349 #[uri_param(default = "1000")]
350 period_ms: u64,
351
352 period: std::time::Duration,
353 }
354
355 #[test]
356 fn test_duration_from_ms_field() {
357 let config = TimerTestConfig::from_uri("timer:tick?period_ms=500").unwrap();
358 assert_eq!(config.name, "tick");
359 assert_eq!(config.period, std::time::Duration::from_millis(500));
360 }
361
362 #[test]
363 fn test_duration_uses_default() {
364 let config = TimerTestConfig::from_uri("timer:tick").unwrap();
365 assert_eq!(config.name, "tick");
366 assert_eq!(config.period_ms, 1000);
367 assert_eq!(config.period, std::time::Duration::from_millis(1000));
368 }
369
370 #[derive(Debug, Clone, UriConfig)]
372 #[uri_scheme = "scheduler"]
373 struct SchedulerConfig {
374 task_name: String,
375
376 #[uri_param(default = "5000")]
377 initial_delay_ms: u64,
378
379 #[uri_param(default = "10000")]
380 interval_ms: u64,
381
382 initial_delay: std::time::Duration,
383 interval: std::time::Duration,
384 }
385
386 #[test]
387 fn test_multiple_duration_fields() {
388 let config =
389 SchedulerConfig::from_uri("scheduler:cleanup?initial_delay_ms=2000&interval_ms=3000")
390 .unwrap();
391 assert_eq!(config.task_name, "cleanup");
392 assert_eq!(config.initial_delay_ms, 2000);
393 assert_eq!(config.interval_ms, 3000);
394 assert_eq!(config.initial_delay, std::time::Duration::from_millis(2000));
395 assert_eq!(config.interval, std::time::Duration::from_millis(3000));
396 }
397
398 #[test]
399 fn test_multiple_duration_defaults() {
400 let config = SchedulerConfig::from_uri("scheduler:cleanup").unwrap();
401 assert_eq!(config.task_name, "cleanup");
402 assert_eq!(config.initial_delay_ms, 5000);
403 assert_eq!(config.interval_ms, 10000);
404 assert_eq!(config.initial_delay, std::time::Duration::from_millis(5000));
405 assert_eq!(config.interval, std::time::Duration::from_millis(10000));
406 }
407}