1use serde::{Deserialize, Serialize, Deserializer};
2use std::collections::HashMap;
3use std::sync::Arc;
4use anyhow::Result;
5use schemars::JsonSchema;
6use serde_yaml::Value;
7use serde_json;
8
9#[derive(Debug, Clone)]
11pub enum UniversalValue {
12 Json(serde_json::Value),
13 Yaml(serde_yaml::Value),
14}
15
16impl UniversalValue {
17 pub fn as_json(&self) -> Result<serde_json::Value> {
19 match self {
20 UniversalValue::Json(v) => Ok(v.clone()),
21 UniversalValue::Yaml(v) => {
22 serde_json::to_value(v).map_err(|e| {
24 anyhow::anyhow!("Failed to convert YAML to JSON: {}", e)
25 })
26 }
27 }
28 }
29
30 pub fn as_yaml(&self) -> Result<serde_yaml::Value> {
32 match self {
33 UniversalValue::Yaml(v) => Ok(v.clone()),
34 UniversalValue::Json(v) => {
35 serde_yaml::to_value(v).map_err(|e| {
37 anyhow::anyhow!("Failed to convert JSON to YAML: {}", e)
38 })
39 }
40 }
41 }
42
43 pub fn from_json(value: serde_json::Value) -> Self {
45 UniversalValue::Json(value)
46 }
47
48 pub fn from_yaml(value: serde_yaml::Value) -> Self {
50 UniversalValue::Yaml(value)
51 }
52
53 pub fn to_json_string(&self) -> Result<String> {
55 let json_val = self.as_json()?;
56 serde_json::to_string_pretty(&json_val).map_err(Into::into)
57 }
58
59 pub fn to_yaml_string(&self) -> Result<String> {
61 let yaml_val = self.as_yaml()?;
62 serde_yaml::to_string(&yaml_val).map_err(Into::into)
63 }
64}
65
66fn merge_yaml_missing_only(target: &mut Value, source: Value) {
68 let is_null = matches!(target, Value::Null);
69
70 if let (Value::Mapping(target_map), Value::Mapping(source_map)) = (&mut *target, &source) {
71 for (k, v) in source_map {
72 if !target_map.contains_key(&k) {
73 target_map.insert(k.clone(), v.clone());
75 } else {
76 match (target_map.get_mut(&k), &v) {
78 (Some(Value::Mapping(_)), Value::Mapping(_)) => {
79 let target_value = target_map.get_mut(&k).unwrap();
81 merge_yaml_missing_only(target_value, v.clone());
82 }
83 _ => {
84 }
86 }
87 }
88 }
89 } else if is_null {
90 *target = source;
92 }
93 }
95
96fn merge_json_missing_only(target: &mut serde_json::Value, source: serde_json::Value) {
98 let is_null = matches!(target, serde_json::Value::Null);
99
100 if let (serde_json::Value::Object(target_map), serde_json::Value::Object(source_map)) = (&mut *target, &source) {
101 for (k, v) in source_map {
102 if !target_map.contains_key(k) {
103 target_map.insert(k.clone(), v.clone());
105 } else {
106 let target_value = target_map.get_mut(k).unwrap();
108 match target_value {
109 serde_json::Value::Object(_) => {
110 merge_json_missing_only(target_value, v.clone());
112 }
113 _ => {
114 }
116 }
117 }
118 }
119 } else if is_null {
120 *target = source;
122 }
123 }
125
126fn merge_universal_missing_only(target: &mut UniversalValue, source: UniversalValue) -> Result<()> {
128 match (target, source) {
129 (UniversalValue::Yaml(t), UniversalValue::Yaml(s)) => {
130 merge_yaml_missing_only(t, s);
131 }
132 (UniversalValue::Json(t), UniversalValue::Json(s)) => {
133 merge_json_missing_only(t, s);
134 }
135 (t, s) => {
137 let t_json = t.as_json()?;
138 let s_json = s.as_json()?;
139 let mut merged = t_json;
140 merge_json_missing_only(&mut merged, s_json);
141 *t = UniversalValue::from_json(merged);
142 }
143 }
144 Ok(())
145}
146
147pub fn supplement_middleware_configs(
150 current: &mut Vec<MiddlewareConfig>,
151 incoming: &[MiddlewareConfig],
152) -> Result<()> {
153 let mut index = HashMap::new();
155
156 for (i, m) in current.iter().enumerate() {
157 let name = m.name();
158 index.insert(name.to_string(), i);
159 }
160
161 for new_middleware in incoming {
162 let name = new_middleware.name();
163
164 if let Some(&pos) = index.get(name) {
165 match (&mut current[pos], new_middleware) {
167 (MiddlewareConfig::Named { config: existing_config, .. }, MiddlewareConfig::Named { config, .. }) => {
168 merge_universal_missing_only(existing_config, config.clone())?;
169 }
170 (current_on @ MiddlewareConfig::On { .. }, MiddlewareConfig::Named { name, config }) => {
171 *current_on = MiddlewareConfig::Named {
172 name: name.clone(),
173 config: config.clone(),
174 };
175 }
176 _ => {
178 }
180 }
181 }
182 }
184
185 Ok(())
186}
187
188pub fn filter_disabled_middleware(
192 current: &mut Vec<MiddlewareConfig>,
193 parent: &[MiddlewareConfig],
194) -> Result<()> {
195 let mut parent_index = HashMap::new();
197 for middleware in parent {
198 let name = middleware.name();
199 parent_index.insert(name.to_string(), middleware.is_off());
200 }
201
202 let mut result = Vec::new();
204
205 for middleware in current.iter() {
206 let name = middleware.name();
207
208 match middleware {
209 MiddlewareConfig::Off { .. } => {
210 if let Some(parent_disabled) = parent_index.get(name) {
212 if !parent_disabled {
213 result.push(middleware.clone());
215 }
216 } else {
218 result.push(middleware.clone());
220 }
221 }
222 MiddlewareConfig::Named { .. } | MiddlewareConfig::On { .. } => {
223 result.push(middleware.clone());
225 }
226 }
227 }
228
229 for middleware in parent {
231 let name = middleware.name();
232
233 let exists_in_current = current.iter().any(|m| m.name() == name);
235
236 if !exists_in_current {
237 match middleware {
238 MiddlewareConfig::Named { .. } | MiddlewareConfig::On { .. } => {
239 result.push(middleware.clone());
241 }
242 MiddlewareConfig::Off { .. } => {
243 }
245 }
246 }
247 }
248
249 *current = result;
250 Ok(())
251}
252
253pub fn supplement_middleware(
255 current: &mut Vec<MiddlewareConfig>,
256 incoming: &[MiddlewareConfig],
257) -> Result<()> {
258 let mut index = HashMap::new();
260
261 for (i, m) in current.iter().enumerate() {
262 let name = m.name();
263 index.insert(name.to_string(), i);
264 }
265
266 let mut inherited_prefix: Vec<MiddlewareConfig> = Vec::new();
269
270 for new_middleware in incoming {
271 let name = new_middleware.name();
272
273 if let Some(&pos) = index.get(name) {
274 match (&mut current[pos], new_middleware) {
276 (MiddlewareConfig::Named { config: existing_config, .. }, MiddlewareConfig::Named { config, .. }) => {
277 merge_universal_missing_only(existing_config, config.clone())?;
279 }
280 (current_on @ MiddlewareConfig::On { .. }, MiddlewareConfig::Named { name, config }) => {
281 *current_on = MiddlewareConfig::Named {
283 name: name.clone(),
284 config: config.clone(),
285 };
286 }
287 (MiddlewareConfig::Off { .. }, MiddlewareConfig::Named { .. } | MiddlewareConfig::On { .. }) => {
288 }
292 (MiddlewareConfig::Named { .. } | MiddlewareConfig::On { .. }, MiddlewareConfig::Off { .. }) => {
293 }
296 (MiddlewareConfig::Off { .. }, MiddlewareConfig::Off { .. }) => {
297 }
300 (MiddlewareConfig::Named { .. }, MiddlewareConfig::On { .. }) => {
301 }
303 (MiddlewareConfig::On { .. }, MiddlewareConfig::On { .. }) => {
304 }
306 }
307 } else {
308 if !new_middleware.is_off() {
310 inherited_prefix.push(new_middleware.clone());
311 }
312 }
313 }
314
315 if !inherited_prefix.is_empty() {
316 let mut merged = Vec::with_capacity(inherited_prefix.len() + current.len());
317 merged.extend(inherited_prefix);
318 merged.append(current);
319 *current = merged;
320 }
321
322 Ok(())
323}
324
325impl Serialize for UniversalValue {
327 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
328 where
329 S: serde::Serializer,
330 {
331 match self.as_json() {
332 Ok(json_val) => json_val.serialize(serializer),
333 Err(_) => serializer.serialize_none(),
334 }
335 }
336}
337
338impl<'de> Deserialize<'de> for UniversalValue {
340 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341 where
342 D: Deserializer<'de>,
343 {
344 let yaml_value: serde_yaml::Value = Deserialize::deserialize(deserializer)?;
345 Ok(UniversalValue::Yaml(yaml_value))
346 }
347}
348
349pub type LegacyStrategyCollection = HashMap<String, Vec<MiddlewareConfig>>;
351
352#[derive(Debug, Clone, JsonSchema)]
353pub struct Strategy {
354 pub name: String,
355
356 #[serde(default)]
357 pub middleware: Arc<Vec<MiddlewareConfig>>,
358}
359
360impl<'de> serde::Deserialize<'de> for Strategy {
361 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
362 where
363 D: serde::Deserializer<'de>,
364 {
365 #[derive(serde::Deserialize)]
366 struct Helper {
367 name: String,
368 #[serde(default)]
369 middleware: Vec<MiddlewareConfig>,
370 }
371
372 let helper = Helper::deserialize(deserializer)?;
373 Ok(Strategy {
374 name: helper.name,
375 middleware: Arc::new(helper.middleware),
376 })
377 }
378}
379
380impl serde::Serialize for Strategy {
381 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
382 where
383 S: serde::Serializer,
384 {
385 use serde::ser::SerializeStruct;
386 let mut state = serializer.serialize_struct("Strategy", 2)?;
387 state.serialize_field("name", &self.name)?;
388 state.serialize_field("middleware", &*self.middleware)?;
389 state.end()
390 }
391}
392
393#[derive(Debug, Clone, Serialize, JsonSchema)]
394pub enum MiddlewareConfig {
395 Named {
396 name: String,
397 #[schemars(with = "serde_json::Value")]
398 config: UniversalValue,
399 },
400 On {
401 name: String,
402 },
403 Off {
404 name: String,
405 },
406}
407
408impl MiddlewareConfig {
409 pub fn new_named_json(name: String, config: serde_json::Value) -> Self {
411 MiddlewareConfig::Named {
412 name,
413 config: UniversalValue::from_json(config),
414 }
415 }
416
417 pub fn new_named_yaml(name: String, config: serde_yaml::Value) -> Self {
419 MiddlewareConfig::Named {
420 name,
421 config: UniversalValue::from_yaml(config),
422 }
423 }
424
425 pub fn new_on(name: String) -> Self {
427 MiddlewareConfig::On { name }
428 }
429
430 pub fn new_off(name: String) -> Self {
432 MiddlewareConfig::Off { name }
433 }
434
435 pub fn is_off(&self) -> bool {
437 matches!(self, MiddlewareConfig::Off { .. })
438 }
439
440 pub fn name(&self) -> &str {
442 match self {
443 MiddlewareConfig::Named { name, .. } => name,
444 MiddlewareConfig::On { name } => name,
445 MiddlewareConfig::Off { name } => name,
446 }
447 }
448
449 pub fn config_as_json(&self) -> Result<serde_json::Value> {
451 match self {
452 MiddlewareConfig::Named { config, .. } => config.as_json(),
453 MiddlewareConfig::On { .. } => Ok(serde_json::Value::Object(serde_json::Map::new())),
454 MiddlewareConfig::Off { .. } => Err(anyhow::anyhow!("Cannot get config from disabled middleware")),
455 }
456 }
457
458 pub fn config_as_yaml(&self) -> Result<serde_yaml::Value> {
460 match self {
461 MiddlewareConfig::Named { config, .. } => config.as_yaml(),
462 MiddlewareConfig::On { .. } => Ok(serde_yaml::Value::Mapping(serde_yaml::Mapping::new())),
463 MiddlewareConfig::Off { .. } => Err(anyhow::anyhow!("Cannot get config from disabled middleware")),
464 }
465 }
466
467 pub fn config_to_json_string(&self) -> Result<String> {
469 match self {
470 MiddlewareConfig::Named { config, .. } => config.to_json_string(),
471 MiddlewareConfig::On { .. } => serde_json::to_string_pretty(&serde_json::Value::Object(serde_json::Map::new())).map_err(Into::into),
472 MiddlewareConfig::Off { .. } => Err(anyhow::anyhow!("Cannot get config from disabled middleware")),
473 }
474 }
475
476 pub fn config_to_yaml_string(&self) -> Result<String> {
478 match self {
479 MiddlewareConfig::Named { config, .. } => config.to_yaml_string(),
480 MiddlewareConfig::On { .. } => serde_yaml::to_string(&serde_yaml::Value::Mapping(serde_yaml::Mapping::new())).map_err(Into::into),
481 MiddlewareConfig::Off { .. } => Err(anyhow::anyhow!("Cannot get config from disabled middleware")),
482 }
483 }
484
485 pub fn config_into<T: for<'de> Deserialize<'de>>(&self) -> Result<T> {
487 let json_val = self.config_as_json()?;
488 serde_json::from_value(json_val).map_err(Into::into)
489 }
490
491 pub fn from_serializable<T: Serialize>(name: impl Into<String>, config: T) -> Result<Self> {
503 let json_val = serde_json::to_value(config)
504 .map_err(|e| anyhow::anyhow!("Failed to serialize config: {}", e))?;
505 Ok(MiddlewareConfig::new_named_json(name.into(), json_val))
506 }
507
508 pub fn from_yaml_str(name: impl Into<String>, yaml_str: impl AsRef<str>) -> Result<Self> {
516 let yaml_val = serde_yaml::from_str(yaml_str.as_ref())
517 .map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?;
518 Ok(MiddlewareConfig::new_named_yaml(name.into(), yaml_val))
519 }
520
521 pub fn from_json_str(name: impl Into<String>, json_str: impl AsRef<str>) -> Result<Self> {
529 let json_val = serde_json::from_str(json_str.as_ref())
530 .map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?;
531 Ok(MiddlewareConfig::new_named_json(name.into(), json_val))
532 }
533
534 pub fn parse_config<T: for<'de> Deserialize<'de>>(&self) -> Result<T> {
546 self.config_into()
547 }
548}
549
550impl<'de> Deserialize<'de> for MiddlewareConfig {
551 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
552 where
553 D: Deserializer<'de>,
554 {
555 let map: HashMap<String, serde_yaml::Value> =
556 HashMap::deserialize(deserializer)?;
557
558 if map.len() != 1 {
559 return Err(serde::de::Error::custom(
560 "middleware must contain exactly one key",
561 ));
562 }
563
564 let (name, yaml_value) = map.into_iter().next().unwrap();
565
566 match &yaml_value {
568 serde_yaml::Value::String(s) if s.eq_ignore_ascii_case("off") => {
569 return Ok(MiddlewareConfig::Off { name });
570 }
571 serde_yaml::Value::Bool(b) if !b => {
572 return Ok(MiddlewareConfig::Off { name });
573 }
574 serde_yaml::Value::String(s) if s.eq_ignore_ascii_case("on") => {
575 return Ok(MiddlewareConfig::On { name });
576 }
577 serde_yaml::Value::Bool(b) if *b => {
578 return Ok(MiddlewareConfig::On { name });
579 }
580 _ => {
581 Ok(MiddlewareConfig::Named {
583 name,
584 config: UniversalValue::from_yaml(yaml_value)
585 })
586 }
587 }
588 }
589}
590
591impl Strategy {
592 pub fn new(name: String) -> Self {
593 Self {
594 name,
595 middleware: Arc::new(Vec::new()),
596 }
597 }
598
599 pub fn supplement_with(&mut self, incoming: &[MiddlewareConfig]) -> Result<()> {
601 supplement_middleware(Arc::make_mut(&mut self.middleware), incoming)
602 }
603
604 pub fn merge_with(&self, other: &Strategy) -> Strategy {
605 let mut result = self.clone();
606
607 for middleware in other.middleware.iter() {
608 let merged = Arc::make_mut(&mut result.middleware);
609 if let Some(pos) = merged.iter().position(|existing| existing.name() == middleware.name()) {
610 merged[pos] = middleware.clone();
611 } else {
612 merged.push(middleware.clone());
613 }
614 }
615
616 result
617 }
618}
619
620#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
621#[serde(untagged)]
622pub enum StrategyRef {
623 Named(String),
624 InlineMiddleware(Vec<MiddlewareConfig>),
625}
626
627impl StrategyRef {
628 pub fn resolve(&self, strategies: &LegacyStrategyCollection) -> Option<Strategy> {
629 match self {
630 StrategyRef::Named(name) => strategies.get(name).map(|middleware| Strategy {
631 name: name.clone(),
632 middleware: Arc::new(middleware.clone()),
633 }),
634 StrategyRef::InlineMiddleware(middleware) => Some(Strategy {
635 name: "inline".to_string(),
636 middleware: Arc::new(middleware.clone()),
637 }),
638 }
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645 use serde_json::json;
646
647 #[test]
648 fn test_universal_value_json_conversion() {
649 let json_val = json!({
650 "key": "value",
651 "number": 42,
652 "nested": {
653 "array": [1, 2, 3]
654 }
655 });
656
657 let universal = UniversalValue::from_json(json_val.clone());
658
659 let converted = universal.as_json().unwrap();
661 assert_eq!(json_val, converted);
662 }
663
664 #[test]
665 fn test_universal_value_yaml_conversion() {
666 let yaml_str = r#"key: value
667number: 42
668nested:
669 array: [1, 2, 3]"#;
670
671 let yaml_val: serde_yaml::Value = serde_yaml::from_str(yaml_str).unwrap();
672 let universal = UniversalValue::from_yaml(yaml_val);
673
674 let json_val = universal.as_json().unwrap();
676 assert_eq!(json_val["key"], "value");
677 assert_eq!(json_val["number"], 42);
678 assert_eq!(json_val["nested"]["array"][0], 1);
679 }
680
681 #[test]
682 fn test_universal_value_string_conversion() {
683 let json_val = json!({
684 "message": "Hello, World!",
685 "count": 100
686 });
687
688 let universal = UniversalValue::from_json(json_val);
689
690 let json_str = universal.to_json_string().unwrap();
692 assert!(json_str.contains("Hello, World!"));
693 assert!(json_str.contains("100"));
694
695 let yaml_str = universal.to_yaml_string().unwrap();
697 assert!(yaml_str.contains("message: Hello, World!"));
698 assert!(yaml_str.contains("count: 100"));
699 }
700
701 #[test]
702 fn test_middleware_config_with_universal_value() {
703 let json_config = json!({
704 "requests": 1000,
705 "window": "1m"
706 });
707
708 let middleware = MiddlewareConfig::new_named_json(
709 "rate_limit".to_string(),
710 json_config.clone()
711 );
712
713 assert_eq!(middleware.name(), "rate_limit");
714
715 let retrieved_json = middleware.config_as_json().unwrap();
717 assert_eq!(json_config, retrieved_json);
718
719 #[derive(Deserialize)]
721 struct RateLimitConfig {
722 requests: u32,
723 window: String,
724 }
725
726 let rate_limit: RateLimitConfig = middleware.config_into().unwrap();
727 assert_eq!(rate_limit.requests, 1000);
728 assert_eq!(rate_limit.window, "1m");
729 }
730
731 #[test]
732 fn test_middleware_config_yaml_deserialization() {
733 let yaml_str = r#"
734- rate_limit:
735 requests: 500
736 window: "30s"
737- logging:
738 level: info
739 format: json
740"#;
741
742 let middleware: Vec<MiddlewareConfig> = serde_yaml::from_str(yaml_str).unwrap();
743 assert_eq!(middleware.len(), 2);
744
745 let rate_limit = &middleware[0];
747 assert_eq!(rate_limit.name(), "rate_limit");
748
749 let config_json = rate_limit.config_as_json().unwrap();
750 assert_eq!(config_json["requests"], 500);
751 assert_eq!(config_json["window"], "30s");
752
753 let logging = &middleware[1];
755 assert_eq!(logging.name(), "logging");
756
757 let config_json = logging.config_as_json().unwrap();
758 assert_eq!(config_json["level"], "info");
759 assert_eq!(config_json["format"], "json");
760 }
761
762 #[test]
763 fn test_strategy_with_universal_values() {
764 let yaml_strategy = r#"
765name: "test_strategy"
766middleware:
767 - auth:
768 type: jwt
769 secret: "my-secret"
770 - cors:
771 origins: ["*"]
772 methods: ["GET", "POST"]
773"#;
774
775 let strategy: Strategy = serde_yaml::from_str(yaml_strategy).unwrap();
776 assert_eq!(strategy.name, "test_strategy");
777 assert_eq!(strategy.middleware.len(), 2);
778
779 let auth = &strategy.middleware[0];
781 assert_eq!(auth.name(), "auth");
782
783 #[derive(Deserialize)]
784 struct AuthConfig {
785 r#type: String,
786 secret: String,
787 }
788
789 let auth_config: AuthConfig = auth.config_into().unwrap();
790 assert_eq!(auth_config.r#type, "jwt");
791 assert_eq!(auth_config.secret, "my-secret");
792
793 let cors = &strategy.middleware[1];
795 assert_eq!(cors.name(), "cors");
796
797 let cors_json = cors.config_as_json().unwrap();
798 assert_eq!(cors_json["origins"][0], "*");
799 assert_eq!(cors_json["methods"][0], "GET");
800 assert_eq!(cors_json["methods"][1], "POST");
801 }
802
803 #[test]
804 fn test_strategy_ref_inline_middleware_collection() {
805 let inline_yaml = r#"
807 - rate_limit:
808 requests: 100
809 window: "1m"
810 - logging:
811 level: debug
812 format: json
813"#;
814
815 let strategy_ref: StrategyRef = serde_yaml::from_str(inline_yaml).unwrap();
816 let resolved = strategy_ref.resolve(&LegacyStrategyCollection::new());
817
818 assert!(resolved.is_some());
819 let strategy = resolved.unwrap();
820 assert_eq!(strategy.name, "inline");
821 assert_eq!(strategy.middleware.len(), 2);
822
823 let rate_limit = &strategy.middleware[0];
825 assert_eq!(rate_limit.name(), "rate_limit");
826
827 #[derive(Deserialize)]
828 struct RateLimitConfig {
829 requests: u32,
830 window: String,
831 }
832
833 let rate_config: RateLimitConfig = rate_limit.config_into().unwrap();
834 assert_eq!(rate_config.requests, 100);
835 assert_eq!(rate_config.window, "1m");
836
837 let logging = &strategy.middleware[1];
839 assert_eq!(logging.name(), "logging");
840
841 let logging_json = logging.config_as_json().unwrap();
842 assert_eq!(logging_json["level"], "debug");
843 assert_eq!(logging_json["format"], "json");
844 }
845
846 #[test]
847 fn test_strategy_ref_named_vs_inline() {
848 let mut strategies = LegacyStrategyCollection::new();
849
850 strategies.insert("test".to_string(), vec![
852 MiddlewareConfig::new_named_json(
853 "auth".to_string(),
854 json!({"type": "basic"})
855 )
856 ]);
857
858 let named_ref = StrategyRef::Named("test".to_string());
860 let named_resolved = named_ref.resolve(&strategies).unwrap();
861 assert_eq!(named_resolved.name, "test");
862 assert_eq!(named_resolved.middleware.len(), 1);
863
864 let inline_middleware = vec![
866 MiddlewareConfig::new_named_json(
867 "logging".to_string(),
868 json!({"level": "info"})
869 )
870 ];
871 let inline_ref = StrategyRef::InlineMiddleware(inline_middleware);
872 let inline_resolved = inline_ref.resolve(&strategies).unwrap();
873 assert_eq!(inline_resolved.name, "inline");
874 assert_eq!(inline_resolved.middleware.len(), 1);
875 }
876
877 #[test]
878 fn test_universal_value_roundtrip() {
879 let original = json!({
880 "string": "test",
881 "number": 42,
882 "boolean": true,
883 "null": null,
884 "array": [1, 2, 3],
885 "object": {
886 "nested": "value"
887 }
888 });
889
890 let universal1 = UniversalValue::from_json(original.clone());
892 let yaml_val = universal1.as_yaml().unwrap();
893 let universal2 = UniversalValue::from_yaml(yaml_val);
894 let final_json = universal2.as_json().unwrap();
895
896 let original_str = serde_json::to_string(&original).unwrap();
898 let final_str = serde_json::to_string(&final_json).unwrap();
899
900 let parsed_original: serde_json::Value = serde_json::from_str(&original_str).unwrap();
902 let parsed_final: serde_json::Value = serde_json::from_str(&final_str).unwrap();
903
904 assert_eq!(parsed_original, parsed_final);
905 }
906
907 #[test]
908 fn test_supplement_middleware_missing_only() {
909 let mut current = vec![
910 MiddlewareConfig::new_named_json(
911 "rate_limit".to_string(),
912 json!({
913 "requests": 1000,
914 "window": "1m",
915 "burst": 100
916 })
917 ),
918 MiddlewareConfig::new_named_json(
919 "logging".to_string(),
920 json!({
921 "level": "info",
922 "format": "text"
923 })
924 )
925 ];
926
927 let incoming = vec![
928 MiddlewareConfig::new_named_json(
929 "rate_limit".to_string(),
930 json!({
931 "requests": 2000, "timeout": "30s" })
934 ),
935 MiddlewareConfig::new_named_json(
936 "cors".to_string(),
937 json!({
938 "origins": ["*"],
939 "methods": ["GET", "POST"]
940 })
941 )
942 ];
943
944 supplement_middleware(&mut current, &incoming).unwrap();
945
946 assert_eq!(current.len(), 3); assert_eq!(current[0].name(), "cors");
949
950 let rate_limit = current.iter().find(|m| m.name() == "rate_limit").unwrap();
952 assert_eq!(rate_limit.name(), "rate_limit");
953 let config = rate_limit.config_as_json().unwrap();
954 assert_eq!(config["requests"], 1000); assert_eq!(config["window"], "1m"); assert_eq!(config["burst"], 100); assert_eq!(config["timeout"], "30s"); let logging = current.iter().find(|m| m.name() == "logging").unwrap();
961 assert_eq!(logging.name(), "logging");
962 let config = logging.config_as_json().unwrap();
963 assert_eq!(config["level"], "info");
964 assert_eq!(config["format"], "text");
965
966 let cors = current.iter().find(|m| m.name() == "cors").unwrap();
968 assert_eq!(cors.name(), "cors");
969 let config = cors.config_as_json().unwrap();
970 assert_eq!(config["origins"][0], "*");
971 assert_eq!(config["methods"][0], "GET");
972 }
973
974 #[test]
975 fn test_strategy_supplement_with_method() {
976 let mut strategy = Strategy::new("test".to_string());
977 Arc::make_mut(&mut strategy.middleware).push(
978 MiddlewareConfig::new_named_json(
979 "auth".to_string(),
980 json!({
981 "type": "basic",
982 "realm": "protected"
983 })
984 )
985 );
986
987 let incoming = vec![
988 MiddlewareConfig::new_named_json(
989 "auth".to_string(),
990 json!({
991 "timeout": 300, "max_attempts": 5, "type": "jwt" })
995 ),
996 MiddlewareConfig::new_named_json(
997 "rate_limit".to_string(),
998 json!({
999 "requests": 100
1000 })
1001 )
1002 ];
1003
1004 strategy.supplement_with(&incoming).unwrap();
1005
1006 assert_eq!(strategy.middleware.len(), 2);
1007
1008 let auth = strategy.middleware.iter().find(|m| m.name() == "auth").unwrap();
1010 assert_eq!(auth.name(), "auth");
1011 let config = auth.config_as_json().unwrap();
1012 assert_eq!(config["type"], "basic"); assert_eq!(config["realm"], "protected"); assert_eq!(config["timeout"], 300); assert_eq!(config["max_attempts"], 5); assert_eq!(strategy.middleware[0].name(), "rate_limit");
1019 let rate_limit = &strategy.middleware[0];
1020 assert_eq!(rate_limit.name(), "rate_limit");
1021 let config = rate_limit.config_as_json().unwrap();
1022 assert_eq!(config["requests"], 100);
1023 }
1024
1025 #[test]
1026 fn test_supplement_middleware_yaml_formats() {
1027 let mut current = vec![
1028 MiddlewareConfig::new_named_yaml(
1029 "cache".to_string(),
1030 serde_yaml::from_str(r#"
1031ttl: 300
1032max_size: 1000
1033"#).unwrap()
1034 )
1035 ];
1036
1037 let incoming = vec![
1038 MiddlewareConfig::new_named_json(
1039 "cache".to_string(),
1040 json!({
1041 "ttl": 600, "strategy": "lru" })
1044 )
1045 ];
1046
1047 supplement_middleware(&mut current, &incoming).unwrap();
1048
1049 let cache = ¤t[0];
1050 assert_eq!(cache.name(), "cache");
1051 let config = cache.config_as_json().unwrap();
1052 assert_eq!(config["ttl"], 300); assert_eq!(config["max_size"], 1000); assert_eq!(config["strategy"], "lru"); }
1056
1057 #[test]
1058 fn test_supplement_middleware_no_overwrite() {
1059 let mut current = vec![
1060 MiddlewareConfig::new_named_json(
1061 "test".to_string(),
1062 json!({
1063 "existing_string": "old_value",
1064 "existing_number": 42,
1065 "existing_bool": true,
1066 "existing_null": null,
1067 "existing_array": [1, 2, 3],
1068 "existing_object": {"key": "value"}
1069 })
1070 )
1071 ];
1072
1073 let incoming = vec![
1074 MiddlewareConfig::new_named_json(
1075 "test".to_string(),
1076 json!({
1077 "existing_string": "new_value", "existing_number": 100, "existing_bool": false, "existing_null": "not_null", "existing_array": [4, 5, 6], "existing_object": {"new_key": "new_value"}, "new_string": "added", "new_number": 999, "new_bool": false, "new_array": [7, 8, 9], "new_object": {"added": "yes"} })
1089 )
1090 ];
1091
1092 supplement_middleware(&mut current, &incoming).unwrap();
1093
1094 let test = ¤t[0];
1095 let config = test.config_as_json().unwrap();
1096
1097 assert_eq!(config["existing_string"], "old_value");
1099 assert_eq!(config["existing_number"], 42);
1100 assert_eq!(config["existing_bool"], true);
1101 assert_eq!(config["existing_null"], serde_json::Value::Null);
1102 assert_eq!(config["existing_array"], json!([1, 2, 3]));
1103 assert_eq!(config["existing_object"], json!({"key": "value", "new_key": "new_value"}));
1104
1105 assert_eq!(config["new_string"], "added");
1107 assert_eq!(config["new_number"], 999);
1108 assert_eq!(config["new_bool"], false);
1109 assert_eq!(config["new_array"], json!([7, 8, 9]));
1110 assert_eq!(config["new_object"], json!({"added": "yes"}));
1111 }
1112
1113 #[test]
1114 fn test_inline_middleware_strategy_ref() {
1115 let yaml_str = r#"
1116 - rate_limit:
1117 requests: 100
1118 window: "1m"
1119 - logging:
1120 level: debug
1121 "#;
1122
1123 let strategy_ref: StrategyRef = serde_yaml::from_str(yaml_str).unwrap();
1124
1125 match strategy_ref {
1126 StrategyRef::InlineMiddleware(middleware) => {
1127 assert_eq!(middleware.len(), 2);
1128 assert_eq!(middleware[0].name(), "rate_limit");
1129 assert_eq!(middleware[1].name(), "logging");
1130 }
1131 _ => panic!("Expected InlineMiddleware variant"),
1132 }
1133 }
1134
1135 #[test]
1136 fn test_inline_middleware_strategy_ref_resolution() {
1137 let yaml_str = r#"
1138 - rate_limit:
1139 requests: 50
1140 window: "30s"
1141 - logging:
1142 level: info
1143 "#;
1144
1145 let strategy_ref: StrategyRef = serde_yaml::from_str(yaml_str).unwrap();
1146 let strategies = LegacyStrategyCollection::new();
1147
1148 let resolved = strategy_ref.resolve(&strategies).unwrap();
1149 assert_eq!(resolved.name, "inline");
1150 assert_eq!(resolved.middleware.len(), 2);
1151 assert_eq!(resolved.middleware[0].name(), "rate_limit");
1152 assert_eq!(resolved.middleware[1].name(), "logging");
1153 }
1154
1155 #[test]
1156 fn test_from_yaml_str_convenience() {
1157 let middleware = MiddlewareConfig::from_yaml_str(
1158 "httpward_log_module",
1159 "level: warn"
1160 ).unwrap();
1161
1162 assert_eq!(middleware.name(), "httpward_log_module");
1163 assert!(!middleware.is_off());
1164
1165 let config: serde_json::Value = middleware.config_as_json().unwrap();
1166 assert_eq!(config["level"], "warn");
1167 }
1168
1169 #[test]
1170 fn test_from_json_str_convenience() {
1171 let middleware = MiddlewareConfig::from_json_str(
1172 "httpward_log_module",
1173 r#"{"level": "info", "tag": "api"}"#
1174 ).unwrap();
1175
1176 assert_eq!(middleware.name(), "httpward_log_module");
1177 assert!(!middleware.is_off());
1178
1179 let config: serde_json::Value = middleware.config_as_json().unwrap();
1180 assert_eq!(config["level"], "info");
1181 assert_eq!(config["tag"], "api");
1182 }
1183
1184 #[test]
1185 fn test_from_serializable_convenience() {
1186 #[derive(Serialize)]
1187 struct LogConfig {
1188 level: String,
1189 tag: Option<String>,
1190 }
1191
1192 let config = LogConfig {
1193 level: "debug".to_string(),
1194 tag: Some("test".to_string()),
1195 };
1196
1197 let middleware = MiddlewareConfig::from_serializable(
1198 "httpward_log_module",
1199 config
1200 ).unwrap();
1201
1202 assert_eq!(middleware.name(), "httpward_log_module");
1203 assert!(!middleware.is_off());
1204
1205 let parsed_config: serde_json::Value = middleware.config_as_json().unwrap();
1206 assert_eq!(parsed_config["level"], "debug");
1207 assert_eq!(parsed_config["tag"], "test");
1208 }
1209
1210 #[test]
1211 fn test_parse_config_convenience() {
1212 #[derive(Deserialize, PartialEq, Debug)]
1213 struct LogConfig {
1214 level: String,
1215 tag: Option<String>,
1216 }
1217
1218 let middleware = MiddlewareConfig::from_yaml_str(
1219 "httpward_log_module",
1220 "level: warn\ntag: test"
1221 ).unwrap();
1222
1223 let config: LogConfig = middleware.parse_config().unwrap();
1224 assert_eq!(config.level, "warn");
1225 assert_eq!(config.tag, Some("test".to_string()));
1226 }
1227
1228 #[test]
1229 fn test_yaml_round_trip_convenience() {
1230 let original = MiddlewareConfig::from_yaml_str(
1231 "test_middleware",
1232 "level: warn\ntag: test"
1233 ).unwrap();
1234
1235 let yaml_string = original.config_to_yaml_string().unwrap();
1236 let restored = MiddlewareConfig::from_yaml_str("test_middleware", &yaml_string).unwrap();
1237
1238 let original_config: serde_json::Value = original.config_as_json().unwrap();
1239 let restored_config: serde_json::Value = restored.config_as_json().unwrap();
1240
1241 assert_eq!(original_config, restored_config);
1242 }
1243
1244 #[test]
1245 fn test_json_round_trip_convenience() {
1246 let original = MiddlewareConfig::from_json_str(
1247 "test_middleware",
1248 r#"{"level": "warn", "tag": "test"}"#
1249 ).unwrap();
1250
1251 let json_string = original.config_to_json_string().unwrap();
1252 let restored = MiddlewareConfig::from_json_str("test_middleware", &json_string).unwrap();
1253
1254 let original_config: serde_json::Value = original.config_as_json().unwrap();
1255 let restored_config: serde_json::Value = restored.config_as_json().unwrap();
1256
1257 assert_eq!(original_config, restored_config);
1258 }
1259}
1260
1261mod off_tests;