1use crate::ConfigError;
10use serde::{Deserialize, Deserializer, Serialize};
11use std::fmt;
12use std::str::FromStr;
13
14#[derive(Debug, Clone, Serialize, PartialEq)]
23#[serde(untagged)]
24pub enum Value {
25 Null,
28
29 Bool(bool),
32
33 Integer(i64),
36
37 Float(f64),
40
41 String(String),
44
45 List(Vec<Value>),
48
49 Object(indexmap::IndexMap<String, Value>),
52}
53
54impl Value {
55 pub fn null() -> Self {
58 Value::Null
59 }
60
61 pub fn bool(v: bool) -> Self {
64 Value::Bool(v)
65 }
66
67 pub fn integer(v: i64) -> Self {
70 Value::Integer(v)
71 }
72
73 pub fn float(v: f64) -> Self {
76 Value::Float(v)
77 }
78
79 pub fn string(v: impl Into<String>) -> Self {
82 Value::String(v.into())
83 }
84
85 pub fn list(v: Vec<Value>) -> Self {
88 Value::List(v)
89 }
90
91 pub fn object(v: indexmap::IndexMap<String, Value>) -> Self {
94 Value::Object(v)
95 }
96
97 pub fn is_null(&self) -> bool {
100 matches!(self, Value::Null)
101 }
102
103 pub fn is_bool(&self) -> bool {
106 matches!(self, Value::Bool(_))
107 }
108
109 pub fn is_integer(&self) -> bool {
112 matches!(self, Value::Integer(_))
113 }
114
115 pub fn is_float(&self) -> bool {
118 matches!(self, Value::Float(_))
119 }
120
121 pub fn is_string(&self) -> bool {
124 matches!(self, Value::String(_))
125 }
126
127 pub fn is_list(&self) -> bool {
130 matches!(self, Value::List(_))
131 }
132
133 pub fn is_object(&self) -> bool {
136 matches!(self, Value::Object(_))
137 }
138
139 pub fn as_bool(&self) -> Option<bool> {
142 match self {
143 Value::Bool(v) => Some(*v),
144 Value::String(s) => s.parse::<bool>().ok(),
145 Value::Integer(v) => Some(*v != 0),
146 Value::Float(v) => Some(*v != 0.0),
147 _ => None,
148 }
149 }
150
151 pub fn as_i64(&self) -> Option<i64> {
154 match self {
155 Value::Integer(v) => Some(*v),
156 Value::Float(v) => Some(*v as i64),
157 Value::String(s) => s.parse::<i64>().ok(),
158 Value::Bool(v) => Some(*v as i64),
159 _ => None,
160 }
161 }
162
163 #[allow(clippy::cast_precision_loss)]
166 pub fn as_f64(&self) -> Option<f64> {
167 match self {
168 Value::Float(v) => Some(*v),
169 Value::Integer(v) => Some(*v as f64),
170 Value::String(s) => s.parse::<f64>().ok(),
171 Value::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
172 _ => None,
173 }
174 }
175
176 pub fn as_str(&self) -> Option<&str> {
179 match self {
180 Value::String(v) => Some(v),
181 Value::Bool(v) => Some(if *v { "true" } else { "false" }),
182 _ => None,
183 }
184 }
185
186 pub fn to_string_value(&self) -> String {
189 match self {
190 Value::String(v) => v.clone(),
191 Value::Bool(v) => (if *v { "true" } else { "false" }).to_string(),
192 Value::Integer(v) => v.to_string(),
193 Value::Float(v) => v.to_string(),
194 Value::Null => "null".to_string(),
195 Value::List(v) => format!("{:?}", v),
196 Value::Object(v) => format!("{:?}", v),
197 }
198 }
199
200 pub fn as_list(&self) -> Option<&[Value]> {
203 match self {
204 Value::List(v) => Some(v),
205 _ => None,
206 }
207 }
208
209 pub fn as_object(&self) -> Option<&indexmap::IndexMap<String, Value>> {
212 match self {
213 Value::Object(v) => Some(v),
214 _ => None,
215 }
216 }
217
218 pub fn into<T>(self) -> Result<T, ConfigError>
221 where
222 T: serde::de::DeserializeOwned,
223 {
224 let debug_str = format!("{:?}", self);
225 let type_name = std::any::type_name::<T>();
226
227 let json_value = match &self {
230 Value::String(s) => {
231 if type_name.contains("u8")
233 || type_name.contains("u16")
234 || type_name.contains("u32")
235 || type_name.contains("u64")
236 || type_name.contains("i8")
237 || type_name.contains("i16")
238 || type_name.contains("i32")
239 || type_name.contains("i64")
240 || type_name.contains("usize")
241 || type_name.contains("isize")
242 || type_name.contains("f32")
243 || type_name.contains("f64")
244 {
245 if let Ok(i) = s.parse::<i64>() {
247 serde_json::to_value(i)
248 } else if let Ok(f) = s.parse::<f64>() {
249 serde_json::to_value(f)
250 } else if let Ok(b) = s.parse::<bool>() {
251 serde_json::to_value(b)
252 } else {
253 serde_json::to_value(&self)
255 }
256 } else {
257 serde_json::to_value(&self)
258 }
259 },
260 _ => serde_json::to_value(&self),
261 };
262
263 let json = json_value.map_err(|_e| ConfigError::TypeConversion {
264 key: "unknown".to_string(),
265 expected: type_name.to_string(),
266 value: debug_str.clone(),
267 })?;
268
269 serde_json::from_value(json).map_err(|e| ConfigError::TypeConversion {
270 key: "unknown".to_string(),
271 expected: type_name.to_string(),
272 value: e.to_string(),
273 })
274 }
275}
276
277impl From<bool> for Value {
279 fn from(v: bool) -> Self {
280 Value::Bool(v)
281 }
282}
283
284impl From<i8> for Value {
285 fn from(v: i8) -> Self {
286 Value::Integer(v as i64)
287 }
288}
289
290impl From<i16> for Value {
291 fn from(v: i16) -> Self {
292 Value::Integer(v as i64)
293 }
294}
295
296impl From<i32> for Value {
297 fn from(v: i32) -> Self {
298 Value::Integer(v as i64)
299 }
300}
301
302impl From<i64> for Value {
303 fn from(v: i64) -> Self {
304 Value::Integer(v)
305 }
306}
307
308impl From<u8> for Value {
309 fn from(v: u8) -> Self {
310 Value::Integer(v as i64)
311 }
312}
313
314impl From<u16> for Value {
315 fn from(v: u16) -> Self {
316 Value::Integer(v as i64)
317 }
318}
319
320impl From<u32> for Value {
321 fn from(v: u32) -> Self {
322 Value::Integer(v as i64)
323 }
324}
325
326impl From<f32> for Value {
327 fn from(v: f32) -> Self {
328 Value::Float(v as f64)
329 }
330}
331
332impl From<f64> for Value {
333 fn from(v: f64) -> Self {
334 Value::Float(v)
335 }
336}
337
338impl From<String> for Value {
339 fn from(v: String) -> Self {
340 Value::String(v)
341 }
342}
343
344impl From<&str> for Value {
345 fn from(v: &str) -> Self {
346 Value::String(v.to_string())
347 }
348}
349
350impl<T> From<Vec<T>> for Value
351where
352 T: Into<Value>,
353{
354 fn from(v: Vec<T>) -> Self {
355 Value::List(v.into_iter().map(Into::into).collect())
356 }
357}
358
359impl<'de> Deserialize<'de> for Value {
360 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
361 where
362 D: Deserializer<'de>,
363 {
364 use serde::de::Error;
365
366 let json_value = serde_json::Value::deserialize(deserializer)?;
368
369 Ok(match json_value {
370 serde_json::Value::Null => Value::Null,
371 serde_json::Value::Bool(v) => Value::Bool(v),
372 serde_json::Value::Number(n) => {
373 if let Some(i) = n.as_i64() {
374 Value::Integer(i)
375 } else if let Some(f) = n.as_f64() {
376 Value::Float(f)
377 } else {
378 return Err(D::Error::custom("Invalid number"));
379 }
380 },
381 serde_json::Value::String(v) => Value::String(v),
382 serde_json::Value::Array(v) => {
383 Value::List(v.into_iter().map(|x| Self::from_json(x)).collect())
384 },
385 serde_json::Value::Object(v) => Value::Object(
386 v.into_iter()
387 .map(|(k, v)| (k, Self::from_json(v)))
388 .collect(),
389 ),
390 })
391 }
392}
393
394impl Value {
395 fn from_json(json: serde_json::Value) -> Self {
398 match json {
399 serde_json::Value::Null => Value::Null,
400 serde_json::Value::Bool(v) => Value::Bool(v),
401 serde_json::Value::Number(n) => {
402 if let Some(i) = n.as_i64() {
403 Value::Integer(i)
404 } else if let Some(f) = n.as_f64() {
405 Value::Float(f)
406 } else {
407 Value::Null
408 }
409 },
410 serde_json::Value::String(v) => Value::String(v),
411 serde_json::Value::Array(v) => {
412 Value::List(v.into_iter().map(|x| Self::from_json(x)).collect())
413 },
414 serde_json::Value::Object(v) => Value::Object(
415 v.into_iter()
416 .map(|(k, v)| (k, Self::from_json(v)))
417 .collect(),
418 ),
419 }
420 }
421}
422
423impl fmt::Display for Value {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 match self {
426 Value::Null => write!(f, "null"),
427 Value::Bool(v) => write!(f, "{v}"),
428 Value::Integer(v) => write!(f, "{v}"),
429 Value::Float(v) => write!(f, "{v}"),
430 Value::String(v) => write!(f, "{v}"),
431 Value::List(v) => write!(f, "{:?}", v),
432 Value::Object(v) => write!(f, "{:?}", v),
433 }
434 }
435}
436
437pub struct ValueExtractor;
452
453impl ValueExtractor {
454 pub fn extract<T>(
457 key: &str,
458 default: Option<T>,
459 env: &crate::Environment,
460 ) -> Result<T, ConfigError>
461 where
462 T: FromStr + serde::de::DeserializeOwned,
463 T::Err: fmt::Display,
464 {
465 if let Some(value) = env.get_property(key)
466 && let Ok(parsed) = value.into::<T>()
467 {
468 return Ok(parsed);
469 }
470
471 default.ok_or_else(|| ConfigError::MissingProperty(key.to_string()))
472 }
473
474 pub fn extract_string(
477 key: &str,
478 default: Option<&str>,
479 env: &crate::Environment,
480 ) -> Result<String, ConfigError> {
481 if let Some(value) = env.get_property(key)
482 && let Some(s) = value.as_str()
483 {
484 return Ok(s.to_string());
485 }
486
487 default
488 .map(ToString::to_string)
489 .ok_or_else(|| ConfigError::MissingProperty(key.to_string()))
490 }
491
492 pub fn extract_bool(
495 key: &str,
496 default: Option<bool>,
497 env: &crate::Environment,
498 ) -> Result<bool, ConfigError> {
499 if let Some(value) = env.get_property(key)
500 && let Some(b) = value.as_bool()
501 {
502 return Ok(b);
503 }
504
505 default.ok_or_else(|| ConfigError::MissingProperty(key.to_string()))
506 }
507
508 pub fn extract_int<T>(
511 key: &str,
512 default: Option<T>,
513 env: &crate::Environment,
514 ) -> Result<T, ConfigError>
515 where
516 T: FromStr + serde::de::DeserializeOwned,
517 T::Err: fmt::Display,
518 {
519 if let Some(value) = env.get_property(key)
520 && let Some(i) = value.as_i64()
521 && let Ok(parsed) = i.to_string().parse::<T>()
522 {
523 return Ok(parsed);
524 }
525
526 default.ok_or_else(|| ConfigError::MissingProperty(key.to_string()))
527 }
528
529 pub fn parse_placeholder(input: &str) -> (String, Option<String>) {
532 let input = input.trim();
533
534 if !input.starts_with("${") || !input.ends_with('}') {
535 return (input.to_string(), None);
536 }
537
538 let inner = &input[2..input.len() - 1];
539
540 if let Some(colon_pos) = inner.find(':') {
541 let key = inner[..colon_pos].trim().to_string();
542 let default = inner[colon_pos + 1..].trim().to_string();
543 (key, Some(default))
544 } else {
545 (inner.trim().to_string(), None)
546 }
547 }
548
549 pub fn resolve_placeholder(input: &str, env: &crate::Environment) -> String {
552 let (key, default) = Self::parse_placeholder(input);
553
554 if let Some(value) = env.get_property(&key)
555 && let Some(s) = value.as_str()
556 {
557 return s.to_string();
558 }
559
560 default.unwrap_or_default()
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use crate::{Environment, PropertySource};
568
569 #[test]
576 fn test_value_null() {
577 let v = Value::null();
578 assert!(v.is_null());
579 assert!(!v.is_bool());
580 assert!(!v.is_string());
581 }
582
583 #[test]
586 fn test_value_bool() {
587 let t = Value::bool(true);
588 let f = Value::bool(false);
589 assert!(t.is_bool());
590 assert!(f.is_bool());
591 assert_eq!(t.as_bool(), Some(true));
592 assert_eq!(f.as_bool(), Some(false));
593 }
594
595 #[test]
598 fn test_value_integer() {
599 let v = Value::integer(42);
600 assert!(v.is_integer());
601 assert_eq!(v.as_i64(), Some(42));
602 assert_eq!(v.as_f64(), Some(42.0));
603 }
604
605 #[test]
608 fn test_value_float() {
609 let v = Value::float(3.14);
610 assert!(v.is_float());
611 assert_eq!(v.as_f64(), Some(3.14));
612 }
613
614 #[test]
617 fn test_value_string() {
618 let v = Value::string("hello");
619 assert!(v.is_string());
620 assert_eq!(v.as_str(), Some("hello"));
621 }
622
623 #[test]
626 fn test_value_list() {
627 let v = Value::list(vec![Value::integer(1), Value::integer(2), Value::integer(3)]);
628 assert!(v.is_list());
629 assert_eq!(v.as_list().map(|l| l.len()), Some(3));
630 }
631
632 #[test]
635 fn test_value_object() {
636 let mut map = indexmap::IndexMap::new();
637 map.insert("name".to_string(), Value::string("hiver"));
638 map.insert("port".to_string(), Value::integer(8080));
639 let v = Value::object(map);
640 assert!(v.is_object());
641 assert_eq!(v.as_object().map(|o| o.len()), Some(2));
642 }
643
644 #[test]
651 fn test_as_bool_coercion() {
652 assert_eq!(Value::string("true").as_bool(), Some(true));
654 assert_eq!(Value::string("false").as_bool(), Some(false));
655
656 assert_eq!(Value::integer(0).as_bool(), Some(false));
658 assert_eq!(Value::integer(1).as_bool(), Some(true));
659 assert_eq!(Value::integer(-1).as_bool(), Some(true));
660
661 assert_eq!(Value::float(0.0).as_bool(), Some(false));
663 assert_eq!(Value::float(1.5).as_bool(), Some(true));
664
665 assert_eq!(Value::null().as_bool(), None);
667 assert_eq!(Value::list(vec![]).as_bool(), None);
668 }
669
670 #[test]
673 fn test_as_i64_coercion() {
674 assert_eq!(Value::integer(42).as_i64(), Some(42));
675 assert_eq!(Value::float(3.9).as_i64(), Some(3));
676 assert_eq!(Value::string("100").as_i64(), Some(100));
677 assert_eq!(Value::bool(true).as_i64(), Some(1));
678 assert_eq!(Value::bool(false).as_i64(), Some(0));
679 assert_eq!(Value::null().as_i64(), None);
680 assert_eq!(Value::string("not_a_number").as_i64(), None);
681 }
682
683 #[test]
686 fn test_as_f64_coercion() {
687 assert_eq!(Value::float(2.5).as_f64(), Some(2.5));
688 assert_eq!(Value::integer(10).as_f64(), Some(10.0));
689 assert_eq!(Value::string("3.14").as_f64(), Some(3.14));
690 assert_eq!(Value::bool(true).as_f64(), Some(1.0));
691 assert_eq!(Value::bool(false).as_f64(), Some(0.0));
692 assert_eq!(Value::null().as_f64(), None);
693 }
694
695 #[test]
698 fn test_as_str_variants() {
699 assert_eq!(Value::string("hello").as_str(), Some("hello"));
700 assert_eq!(Value::bool(true).as_str(), Some("true"));
701 assert_eq!(Value::bool(false).as_str(), Some("false"));
702 assert_eq!(Value::integer(42).as_str(), None);
703 assert_eq!(Value::null().as_str(), None);
704 }
705
706 #[test]
709 fn test_to_string_value() {
710 assert_eq!(Value::null().to_string_value(), "null");
711 assert_eq!(Value::bool(true).to_string_value(), "true");
712 assert_eq!(Value::bool(false).to_string_value(), "false");
713 assert_eq!(Value::integer(42).to_string_value(), "42");
714 assert_eq!(Value::float(3.14).to_string_value(), "3.14");
715 assert_eq!(Value::string("hello".to_string()).to_string_value(), "hello");
716 }
717
718 #[test]
721 fn test_into_numeric_from_string() {
722 let v = Value::string("42");
724 assert_eq!(v.clone().into::<i32>().unwrap(), 42i32);
725 assert_eq!(v.clone().into::<u16>().unwrap(), 42u16);
726 assert_eq!(v.clone().into::<i64>().unwrap(), 42i64);
727 }
728
729 #[test]
732 fn test_into_string() {
733 let v = Value::string("hello");
734 let result: String = v.into::<String>().unwrap();
735 assert_eq!(result, "hello");
736 }
737
738 #[test]
741 fn test_into_failure() {
742 let v = Value::string("not_a_number");
743 let result = v.into::<i32>();
744 assert!(result.is_err());
745 }
746
747 #[test]
754 fn test_from_bool() {
755 let v: Value = true.into();
756 assert!(v.is_bool());
757 assert_eq!(v.as_bool(), Some(true));
758 }
759
760 #[test]
763 fn test_from_integers() {
764 let v8: Value = 8i8.into();
765 let v16: Value = 16i16.into();
766 let v32: Value = 32i32.into();
767 let v64: Value = 64i64.into();
768 let vu8: Value = 8u8.into();
769 let vu16: Value = 16u16.into();
770 let vu32: Value = 32u32.into();
771
772 assert!(v8.is_integer());
773 assert_eq!(v8.as_i64(), Some(8));
774 assert!(v16.is_integer());
775 assert_eq!(v16.as_i64(), Some(16));
776 assert!(v32.is_integer());
777 assert_eq!(v32.as_i64(), Some(32));
778 assert!(v64.is_integer());
779 assert_eq!(v64.as_i64(), Some(64));
780 assert!(vu8.is_integer());
781 assert_eq!(vu8.as_i64(), Some(8));
782 assert!(vu16.is_integer());
783 assert_eq!(vu16.as_i64(), Some(16));
784 assert!(vu32.is_integer());
785 assert_eq!(vu32.as_i64(), Some(32));
786 }
787
788 #[test]
791 fn test_from_floats() {
792 let vf32: Value = 1.5f32.into();
793 let vf64: Value = 2.5f64.into();
794 assert!(vf32.is_float());
795 assert!(vf64.is_float());
796 }
797
798 #[test]
801 fn test_from_strings() {
802 let v1: Value = "hello".into();
803 let v2: Value = String::from("world").into();
804 assert!(v1.is_string());
805 assert!(v2.is_string());
806 assert_eq!(v1.as_str(), Some("hello"));
807 assert_eq!(v2.as_str(), Some("world"));
808 }
809
810 #[test]
813 fn test_from_vec() {
814 let v: Value = vec![1i32, 2, 3].into();
815 assert!(v.is_list());
816 assert_eq!(v.as_list().map(|l| l.len()), Some(3));
817 }
818
819 #[test]
826 fn test_display() {
827 assert_eq!(format!("{}", Value::null()), "null");
828 assert_eq!(format!("{}", Value::bool(true)), "true");
829 assert_eq!(format!("{}", Value::integer(42)), "42");
830 assert_eq!(format!("{}", Value::float(1.5)), "1.5");
831 assert_eq!(format!("{}", Value::string("hello")), "hello");
832 }
833
834 #[test]
837 fn test_serde_roundtrip() {
838 let original = Value::string("test_value");
839 let json = serde_json::to_string(&original).unwrap();
840 let deserialized: Value = serde_json::from_str(&json).unwrap();
841 assert_eq!(original, deserialized);
842 }
843
844 #[test]
847 fn test_deserialize_json_null() {
848 let v: Value = serde_json::from_str("null").unwrap();
849 assert!(v.is_null());
850 }
851
852 #[test]
855 fn test_deserialize_json_number() {
856 let vi: Value = serde_json::from_str("42").unwrap();
857 assert!(vi.is_integer());
858 assert_eq!(vi.as_i64(), Some(42));
859
860 let vf: Value = serde_json::from_str("3.14").unwrap();
861 assert!(vf.is_float());
862 }
863
864 #[test]
867 fn test_deserialize_json_object() {
868 let v: Value = serde_json::from_str(r#"{"key": "value", "num": 1}"#).unwrap();
869 assert!(v.is_object());
870 let obj = v.as_object().unwrap();
871 assert_eq!(obj.get("key").unwrap().as_str(), Some("value"));
872 assert_eq!(obj.get("num").unwrap().as_i64(), Some(1));
873 }
874
875 #[test]
878 fn test_deserialize_json_array() {
879 let v: Value = serde_json::from_str("[1, true, \"hello\"]").unwrap();
880 assert!(v.is_list());
881 let list = v.as_list().unwrap();
882 assert_eq!(list.len(), 3);
883 assert_eq!(list[0].as_i64(), Some(1));
884 assert_eq!(list[1].as_bool(), Some(true));
885 assert_eq!(list[2].as_str(), Some("hello"));
886 }
887
888 #[test]
895 fn test_parse_placeholder_with_default() {
896 let (key, default) = ValueExtractor::parse_placeholder("${server.port:8080}");
897 assert_eq!(key, "server.port");
898 assert_eq!(default, Some("8080".to_string()));
899 }
900
901 #[test]
904 fn test_parse_placeholder_without_default() {
905 let (key, default) = ValueExtractor::parse_placeholder("${server.port}");
906 assert_eq!(key, "server.port");
907 assert_eq!(default, None);
908 }
909
910 #[test]
913 fn test_parse_placeholder_non_placeholder() {
914 let (key, default) = ValueExtractor::parse_placeholder("just a string");
915 assert_eq!(key, "just a string");
916 assert_eq!(default, None);
917 }
918
919 #[test]
922 fn test_parse_placeholder_whitespace() {
923 let (key, default) = ValueExtractor::parse_placeholder(" ${ server.host : localhost } ");
924 assert_eq!(key, "server.host");
925 assert_eq!(default, Some("localhost".to_string()));
926 }
927
928 #[test]
931 fn test_resolve_placeholder_from_env() {
932 use crate::{Environment, PropertySource};
933 let env = Environment::new();
934 let mut source = PropertySource::new("test");
935 source.put("app.name", Value::string("hiver"));
936 env.add_property_source(source);
937
938 let result = ValueExtractor::resolve_placeholder("${app.name}", &env);
939 assert_eq!(result, "hiver");
940 }
941
942 #[test]
945 fn test_resolve_placeholder_default() {
946 let env = Environment::new();
947 let result = ValueExtractor::resolve_placeholder("${missing.key:fallback}", &env);
948 assert_eq!(result, "fallback");
949 }
950
951 #[test]
954 fn test_resolve_placeholder_no_default_missing_key() {
955 let env = Environment::new();
956 let result = ValueExtractor::resolve_placeholder("${missing.key}", &env);
957 assert_eq!(result, "");
958 }
959
960 #[test]
963 fn test_extract_string_present() {
964 let env = Environment::new();
965 let mut source = PropertySource::new("test");
966 source.put("greeting", Value::string("hello"));
967 env.add_property_source(source);
968
969 let result = ValueExtractor::extract_string("greeting", None, &env).unwrap();
970 assert_eq!(result, "hello");
971 }
972
973 #[test]
976 fn test_extract_string_default() {
977 let env = Environment::new();
978 let result = ValueExtractor::extract_string("missing", Some("default_val"), &env).unwrap();
979 assert_eq!(result, "default_val");
980 }
981
982 #[test]
985 fn test_extract_string_missing_error() {
986 let env = Environment::new();
987 let result = ValueExtractor::extract_string("nonexistent", None, &env);
988 assert!(result.is_err());
989 }
990
991 #[test]
994 fn test_extract_bool() {
995 let env = Environment::new();
996 let mut source = PropertySource::new("test");
997 source.put("debug", Value::bool(true));
998 env.add_property_source(source);
999
1000 let result = ValueExtractor::extract_bool("debug", None, &env).unwrap();
1001 assert!(result);
1002 }
1003
1004 #[test]
1007 fn test_extract_bool_default() {
1008 let env = Environment::new();
1009 let result = ValueExtractor::extract_bool("missing", Some(false), &env).unwrap();
1010 assert!(!result);
1011 }
1012
1013 #[test]
1016 fn test_extract_int() {
1017 let env = Environment::new();
1018 let mut source = PropertySource::new("test");
1019 source.put("port", Value::integer(9090));
1020 env.add_property_source(source);
1021
1022 let result = ValueExtractor::extract_int::<i32>("port", None, &env).unwrap();
1023 assert_eq!(result, 9090);
1024 }
1025
1026 #[test]
1029 fn test_extract_int_default() {
1030 let env = Environment::new();
1031 let result = ValueExtractor::extract_int::<i32>("missing", Some(8080), &env).unwrap();
1032 assert_eq!(result, 8080);
1033 }
1034
1035 #[test]
1038 fn test_extract_generic() {
1039 let env = Environment::new();
1040 let mut source = PropertySource::new("test");
1041 source.put("count", Value::integer(7));
1042 env.add_property_source(source);
1043
1044 let result = ValueExtractor::extract("count", Some(0i32), &env).unwrap();
1045 assert_eq!(result, 7);
1046 }
1047
1048 #[test]
1051 fn test_extract_generic_default() {
1052 let env = Environment::new();
1053 let result = ValueExtractor::extract("absent", Some(99i32), &env).unwrap();
1054 assert_eq!(result, 99);
1055 }
1056
1057 #[test]
1060 fn test_extract_generic_missing_no_default() {
1061 let env = Environment::new();
1062 let result: Result<i32, _> = ValueExtractor::extract("absent", None, &env);
1063 assert!(result.is_err());
1064 }
1065}