1use std::string::String;
4use std::sync::Arc;
5use std::vec::Vec;
6
7use facet::Facet;
8use facet_reflect::Span;
9use indexmap::IndexMap;
10
11use crate::path::Path;
12use crate::provenance::{ConfigFile, Provenance};
13
14#[derive(Debug, Clone, Facet)]
16#[facet(metadata_container)]
17pub struct Sourced<T> {
18 pub value: T,
20 #[facet(metadata = "span")]
22 pub span: Option<Span>,
23 #[facet(metadata = "other")]
25 pub provenance: Option<Provenance>,
26}
27
28impl<T> Sourced<T> {
29 pub fn new(value: T) -> Self {
31 Self {
32 value,
33 span: None,
34 provenance: None,
35 }
36 }
37
38 pub fn with_provenance(value: T, provenance: Provenance) -> Self {
40 let span = match &provenance {
41 Provenance::File { offset, len, .. } => Some(Span::new(*offset, *len)),
42 _ => None,
43 };
44 Self {
45 value,
46 span,
47 provenance: Some(provenance),
48 }
49 }
50
51 pub fn set_file_provenance(&mut self, file: Arc<ConfigFile>, key_path: impl Into<String>) {
53 if let Some(span) = self.span {
54 self.provenance = Some(Provenance::file(
55 file,
56 key_path,
57 span.offset as usize,
58 span.len as usize,
59 ));
60 }
61 }
62}
63
64pub type ObjectMap = IndexMap<String, ConfigValue, std::hash::RandomState>;
68
69#[derive(Debug, Clone, Facet)]
71pub struct EnumValue {
72 pub variant: String,
74 pub fields: ObjectMap,
76}
77
78#[derive(Debug, Clone, Facet)]
80#[repr(u8)]
81#[facet(untagged)]
82pub enum ConfigValue {
83 Null(Sourced<()>),
85 Bool(Sourced<bool>),
87 Integer(Sourced<i64>),
89 Float(Sourced<f64>),
91 String(Sourced<String>),
93 Array(Sourced<Vec<ConfigValue>>),
95 Object(Sourced<ObjectMap>),
97 Enum(Sourced<EnumValue>),
99}
100
101pub trait ConfigValueVisitor {
102 fn enter_value(&mut self, _path: &Path, _value: &ConfigValue) {}
103 fn exit_value(&mut self, _path: &Path, _value: &ConfigValue) {}
104}
105
106pub trait ConfigValueVisitorMut {
108 fn visit_value(&mut self, _path: &Path, _value: &mut ConfigValue) {}
111}
112
113impl ConfigValue {
114 pub fn visit(&self, visitor: &mut impl ConfigValueVisitor, path: &mut Path) {
116 visitor.enter_value(path, self);
117 match self {
118 ConfigValue::Array(arr) => {
119 for (i, item) in arr.value.iter().enumerate() {
120 path.push(i.to_string());
121 item.visit(visitor, path);
122 path.pop();
123 }
124 }
125 ConfigValue::Object(obj) => {
126 for (key, value) in &obj.value {
127 path.push(key.clone());
128 value.visit(visitor, path);
129 path.pop();
130 }
131 }
132 _ => {}
133 }
134 visitor.exit_value(path, self);
135 }
136
137 pub fn visit_mut(&mut self, visitor: &mut impl ConfigValueVisitorMut, path: &mut Path) {
139 visitor.visit_value(path, self);
140 match self {
141 ConfigValue::Array(arr) => {
142 for (i, item) in arr.value.iter_mut().enumerate() {
143 path.push(i.to_string());
144 item.visit_mut(visitor, path);
145 path.pop();
146 }
147 }
148 ConfigValue::Object(obj) => {
149 for (key, value) in obj.value.iter_mut() {
150 path.push(key.clone());
151 value.visit_mut(visitor, path);
152 path.pop();
153 }
154 }
155 ConfigValue::Enum(e) => {
156 for (key, value) in e.value.fields.iter_mut() {
157 path.push(key.clone());
158 value.visit_mut(visitor, path);
159 path.pop();
160 }
161 }
162 _ => {}
163 }
164 }
165
166 pub fn span(&self) -> Option<Span> {
168 match self {
169 ConfigValue::Null(s) => s.span,
170 ConfigValue::Bool(s) => s.span,
171 ConfigValue::Integer(s) => s.span,
172 ConfigValue::Float(s) => s.span,
173 ConfigValue::String(s) => s.span,
174 ConfigValue::Array(s) => s.span,
175 ConfigValue::Object(s) => s.span,
176 ConfigValue::Enum(s) => s.span,
177 }
178 }
179
180 pub fn span_mut(&mut self) -> &mut Option<Span> {
182 match self {
183 ConfigValue::Null(s) => &mut s.span,
184 ConfigValue::Bool(s) => &mut s.span,
185 ConfigValue::Integer(s) => &mut s.span,
186 ConfigValue::Float(s) => &mut s.span,
187 ConfigValue::String(s) => &mut s.span,
188 ConfigValue::Array(s) => &mut s.span,
189 ConfigValue::Object(s) => &mut s.span,
190 ConfigValue::Enum(s) => &mut s.span,
191 }
192 }
193
194 pub fn provenance(&self) -> Option<&Provenance> {
196 match self {
197 ConfigValue::Null(s) => s.provenance.as_ref(),
198 ConfigValue::Bool(s) => s.provenance.as_ref(),
199 ConfigValue::Integer(s) => s.provenance.as_ref(),
200 ConfigValue::Float(s) => s.provenance.as_ref(),
201 ConfigValue::String(s) => s.provenance.as_ref(),
202 ConfigValue::Array(s) => s.provenance.as_ref(),
203 ConfigValue::Object(s) => s.provenance.as_ref(),
204 ConfigValue::Enum(s) => s.provenance.as_ref(),
205 }
206 }
207
208 pub fn get_by_path(&self, path: &Path) -> Option<&ConfigValue> {
210 let mut current = self;
211 for segment in path {
212 match current {
213 ConfigValue::Object(obj) => {
214 current = obj.value.get(segment)?;
215 }
216 ConfigValue::Array(arr) => {
217 let index: usize = segment.parse().ok()?;
218 current = arr.value.get(index)?;
219 }
220 _ => return None,
221 }
222 }
223 Some(current)
224 }
225
226 pub fn get_by_path_mut(&mut self, path: &Path) -> Option<&mut ConfigValue> {
228 let mut current = self;
229 for segment in path {
230 match current {
231 ConfigValue::Object(obj) => {
232 current = obj.value.get_mut(segment)?;
233 }
234 ConfigValue::Array(arr) => {
235 let index: usize = segment.parse().ok()?;
236 current = arr.value.get_mut(index)?;
237 }
238 _ => return None,
239 }
240 }
241 Some(current)
242 }
243
244 pub fn set_file_provenance_recursive(&mut self, file: &Arc<ConfigFile>, path: &str) {
249 match self {
250 ConfigValue::Null(s) => s.set_file_provenance(file.clone(), path),
251 ConfigValue::Bool(s) => s.set_file_provenance(file.clone(), path),
252 ConfigValue::Integer(s) => s.set_file_provenance(file.clone(), path),
253 ConfigValue::Float(s) => s.set_file_provenance(file.clone(), path),
254 ConfigValue::String(s) => s.set_file_provenance(file.clone(), path),
255 ConfigValue::Array(s) => {
256 s.set_file_provenance(file.clone(), path);
257 for (i, item) in s.value.iter_mut().enumerate() {
258 let item_path = if path.is_empty() {
259 format!("{i}")
260 } else {
261 format!("{path}[{i}]")
262 };
263 item.set_file_provenance_recursive(file, &item_path);
264 }
265 }
266 ConfigValue::Object(s) => {
267 s.set_file_provenance(file.clone(), path);
268 for (key, value) in s.value.iter_mut() {
269 let key_path = if path.is_empty() {
270 key.clone()
271 } else {
272 format!("{path}.{key}")
273 };
274 value.set_file_provenance_recursive(file, &key_path);
275 }
276 }
277 ConfigValue::Enum(s) => {
278 s.set_file_provenance(file.clone(), path);
279 for (key, value) in s.value.fields.iter_mut() {
280 let key_path = if path.is_empty() {
281 key.clone()
282 } else {
283 format!("{path}.{key}")
284 };
285 value.set_file_provenance_recursive(file, &key_path);
286 }
287 }
288 }
289 }
290
291 pub fn extract_subcommand_path(&self, subcommand_field_name: &str) -> Vec<String> {
300 let mut path = Vec::new();
301 self.extract_subcommand_path_recursive(subcommand_field_name, &mut path);
302 path
303 }
304
305 fn extract_subcommand_path_recursive(&self, field_name: &str, path: &mut Vec<String>) {
306 let subcommand_value = match self {
308 ConfigValue::Object(obj) => obj.value.get(field_name),
309 _ => None,
310 };
311
312 if let Some(ConfigValue::Enum(enum_val)) = subcommand_value {
313 path.push(enum_val.value.variant.clone());
315
316 for (_key, value) in &enum_val.value.fields {
319 if let ConfigValue::Enum(nested) = value {
320 path.push(nested.value.variant.clone());
322 for (_k, v) in &nested.value.fields {
324 if matches!(v, ConfigValue::Enum(_)) {
325 let mut synthetic = ObjectMap::default();
327 synthetic.insert("__nested".to_string(), v.clone());
328 let obj = ConfigValue::Object(Sourced::new(synthetic));
329 obj.extract_subcommand_path_recursive("__nested", path);
330 }
331 }
332 break;
333 }
334 }
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use facet_core::Facet;
343
344 #[test]
345 fn test_unit_is_scalar() {
346 let shape = <() as Facet>::SHAPE;
347 assert!(
348 shape.scalar_type().is_some(),
349 "() should have a scalar type: {:?}",
350 shape.scalar_type()
351 );
352 }
353
354 #[test]
355 fn test_sourced_unit_unwraps_to_scalar() {
356 let shape = <Sourced<()> as Facet>::SHAPE;
357 assert!(
358 shape.is_metadata_container(),
359 "Sourced<()> should be a metadata container"
360 );
361
362 let inner = facet_reflect::get_metadata_container_value_shape(shape);
363 assert!(inner.is_some(), "should get inner shape from Sourced<()>");
364
365 let inner = inner.unwrap();
366 assert!(
367 inner.scalar_type().is_some(),
368 "inner shape should be scalar (unit): {:?}",
369 inner.scalar_type()
370 );
371 }
372
373 #[test]
374 fn test_null_variant_classification() {
375 use facet_core::Facet;
376 use facet_solver::VariantsByFormat;
377
378 let shape = <ConfigValue as Facet>::SHAPE;
379 let variants = VariantsByFormat::from_shape(shape).expect("should get variants");
380
381 let null_variant = variants
383 .scalar_variants
384 .iter()
385 .find(|(v, _)| v.name == "Null");
386 assert!(
387 null_variant.is_some(),
388 "Null should be in scalar_variants. Found: {:?}",
389 variants
390 .scalar_variants
391 .iter()
392 .map(|(v, _)| v.name)
393 .collect::<Vec<_>>()
394 );
395
396 let (_, inner_shape) = null_variant.unwrap();
397 assert!(
398 inner_shape.scalar_type().is_some(),
399 "Null's inner_shape should have a scalar type: {:?}",
400 inner_shape.scalar_type()
401 );
402 assert_eq!(
403 inner_shape.scalar_type(),
404 Some(facet_core::ScalarType::Unit),
405 "Null's inner_shape should be Unit type"
406 );
407 }
408
409 #[test]
410 fn test_sourced_is_metadata_container() {
411 let shape = <Sourced<i64> as Facet>::SHAPE;
412 assert!(
413 shape.is_metadata_container(),
414 "Sourced<i64> should be a metadata container"
415 );
416
417 let inner = facet_reflect::get_metadata_container_value_shape(shape);
418 assert!(inner.is_some(), "should get inner shape");
419
420 let inner = inner.unwrap();
421 assert!(
422 inner.scalar_type().is_some(),
423 "inner shape should be scalar (i64)"
424 );
425 }
426
427 #[test]
428 fn test_parse_null() {
429 let json = "null";
430 let value: ConfigValue = facet_json::from_str(json).expect("should parse null");
431 assert!(matches!(value, ConfigValue::Null(_)));
432 }
433
434 #[test]
435 fn test_parse_bool_true() {
436 let json = "true";
437 let value: ConfigValue = facet_json::from_str(json).expect("should parse true");
438 assert!(matches!(value, ConfigValue::Bool(ref s) if s.value));
439 }
440
441 #[test]
442 fn test_parse_bool_false() {
443 let json = "false";
444 let value: ConfigValue = facet_json::from_str(json).expect("should parse false");
445 assert!(matches!(value, ConfigValue::Bool(ref s) if !s.value));
446 }
447
448 #[test]
449 fn test_parse_integer() {
450 let json = "42";
451 let value: ConfigValue = facet_json::from_str(json).expect("should parse integer");
452 assert!(matches!(value, ConfigValue::Integer(ref s) if s.value == 42));
453 }
454
455 #[test]
456 fn test_parse_negative_integer() {
457 let json = "-123";
458 let value: ConfigValue = facet_json::from_str(json).expect("should parse negative integer");
459 assert!(matches!(value, ConfigValue::Integer(ref s) if s.value == -123));
460 }
461
462 #[test]
463 fn test_parse_float() {
464 let json = "3.5";
465 let value: ConfigValue = facet_json::from_str(json).expect("should parse float");
466 assert!(matches!(value, ConfigValue::Float(ref s) if (s.value - 3.5).abs() < 0.001));
467 }
468
469 #[test]
470 fn test_parse_string() {
471 let json = r#""hello""#;
472 let value: ConfigValue = facet_json::from_str(json).expect("should parse string");
473 assert!(matches!(value, ConfigValue::String(ref s) if s.value == "hello"));
474 }
475
476 #[test]
477 fn test_parse_empty_string() {
478 let json = r#""""#;
479 let value: ConfigValue = facet_json::from_str(json).expect("should parse empty string");
480 assert!(matches!(value, ConfigValue::String(ref s) if s.value.is_empty()));
481 }
482
483 #[test]
484 fn test_parse_array() {
485 let json = r#"[1, 2, 3]"#;
486 let value: ConfigValue = facet_json::from_str(json).expect("should parse array");
487 assert!(matches!(value, ConfigValue::Array(ref s) if s.value.len() == 3));
488 }
489
490 #[test]
491 fn test_parse_empty_array() {
492 let json = "[]";
493 let value: ConfigValue = facet_json::from_str(json).expect("should parse empty array");
494 assert!(matches!(value, ConfigValue::Array(ref s) if s.value.is_empty()));
495 }
496
497 #[test]
498 fn test_parse_object() {
499 let json = r#"{"name": "hello", "count": 42}"#;
500 let value: ConfigValue = facet_json::from_str(json).expect("should parse object");
501 assert!(matches!(value, ConfigValue::Object(_)));
502 }
503
504 #[test]
505 fn test_parse_empty_object() {
506 let json = "{}";
507 let value: ConfigValue = facet_json::from_str(json).expect("should parse empty object");
508 assert!(matches!(value, ConfigValue::Object(ref s) if s.value.is_empty()));
509 }
510
511 #[test]
512 fn test_parse_nested_object() {
513 let json = r#"{"outer": {"inner": 42}}"#;
514 let value: ConfigValue = facet_json::from_str(json).expect("should parse nested object");
515 assert!(matches!(value, ConfigValue::Object(_)));
516 }
517
518 #[test]
519 fn test_parse_mixed_array() {
520 let json = r#"[1, "two", true, null]"#;
521 let value: ConfigValue = facet_json::from_str(json).expect("should parse mixed array");
522 if let ConfigValue::Array(arr) = value {
523 assert_eq!(arr.value.len(), 4);
524 assert!(matches!(arr.value[0], ConfigValue::Integer(_)));
525 assert!(matches!(arr.value[1], ConfigValue::String(_)));
526 assert!(matches!(arr.value[2], ConfigValue::Bool(_)));
527 assert!(matches!(arr.value[3], ConfigValue::Null(_)));
528 } else {
529 panic!("expected array");
530 }
531 }
532
533 #[test]
536 fn test_sourced_deserialize_integer() {
537 let json = "42";
539 let result: Result<Sourced<i64>, _> = facet_json::from_str(json);
540 assert!(
541 result.is_ok(),
542 "Sourced<i64> should deserialize: {:?}",
543 result.err()
544 );
545 let sourced = result.unwrap();
546 assert_eq!(sourced.value, 42);
547 assert!(
548 sourced.provenance.is_none(),
549 "provenance should be None after deserialization"
550 );
551 }
552
553 #[test]
554 fn test_sourced_deserialize_string() {
555 let json = r#""hello""#;
556 let result: Result<Sourced<String>, _> = facet_json::from_str(json);
557 assert!(
558 result.is_ok(),
559 "Sourced<String> should deserialize: {:?}",
560 result.err()
561 );
562 let sourced = result.unwrap();
563 assert_eq!(sourced.value, "hello");
564 assert!(sourced.provenance.is_none());
565 }
566
567 #[test]
568 fn test_sourced_with_provenance() {
569 let file = Arc::new(ConfigFile::new("config.json", r#"{"port": 8080}"#));
570 let sourced = Sourced::with_provenance(8080i64, Provenance::file(file, "port", 9, 4));
571
572 assert_eq!(sourced.value, 8080);
573 assert!(sourced.provenance.is_some());
574 assert!(sourced.provenance.as_ref().unwrap().is_file());
575 assert_eq!(sourced.span, Some(Span::new(9, 4)));
577 }
578
579 #[test]
580 fn test_sourced_set_file_provenance() {
581 let mut sourced = Sourced {
583 value: 8080i64,
584 span: Some(Span::new(9, 4)),
585 provenance: None,
586 };
587
588 let file = Arc::new(ConfigFile::new("config.json", r#"{"port": 8080}"#));
589 sourced.set_file_provenance(file.clone(), "port");
590
591 assert!(sourced.provenance.is_some());
592 if let Some(Provenance::File {
593 file: f,
594 key_path,
595 offset,
596 len,
597 }) = &sourced.provenance
598 {
599 assert_eq!(f.path.as_str(), "config.json");
600 assert_eq!(key_path, "port");
601 assert_eq!(*offset, 9);
602 assert_eq!(*len, 4);
603 } else {
604 panic!("expected File provenance");
605 }
606 }
607
608 #[test]
609 fn test_set_file_provenance_recursive() {
610 let json = r#"{"port": 8080, "smtp": {"host": "mail.example.com", "port": 587}}"#;
612 let mut value: ConfigValue = facet_json::from_str(json).expect("should parse");
613
614 let file = Arc::new(ConfigFile::new("config.json", json));
616 value.set_file_provenance_recursive(&file, "");
617
618 if let ConfigValue::Object(ref obj) = value {
620 assert!(obj.provenance.is_some());
621
622 if let Some(ConfigValue::Integer(port)) = obj.value.get("port") {
624 assert!(port.provenance.is_some());
625 if let Some(Provenance::File { key_path, .. }) = &port.provenance {
626 assert_eq!(key_path, "port");
627 } else {
628 panic!("expected File provenance for port");
629 }
630 } else {
631 panic!("expected port field");
632 }
633
634 if let Some(ConfigValue::Object(smtp)) = obj.value.get("smtp") {
636 assert!(smtp.provenance.is_some());
637 if let Some(Provenance::File { key_path, .. }) = &smtp.provenance {
638 assert_eq!(key_path, "smtp");
639 }
640
641 if let Some(ConfigValue::String(host)) = smtp.value.get("host") {
642 assert!(host.provenance.is_some());
643 if let Some(Provenance::File { key_path, .. }) = &host.provenance {
644 assert_eq!(key_path, "smtp.host");
645 } else {
646 panic!("expected File provenance for smtp.host");
647 }
648 } else {
649 panic!("expected smtp.host field");
650 }
651 } else {
652 panic!("expected smtp field");
653 }
654 } else {
655 panic!("expected object");
656 }
657 }
658
659 fn cv_object(fields: impl IntoIterator<Item = (&'static str, ConfigValue)>) -> ConfigValue {
661 let map: ObjectMap = fields
662 .into_iter()
663 .map(|(k, v)| (k.to_string(), v))
664 .collect();
665 ConfigValue::Object(Sourced::new(map))
666 }
667
668 fn cv_enum(
670 variant: &str,
671 fields: impl IntoIterator<Item = (&'static str, ConfigValue)>,
672 ) -> ConfigValue {
673 let map: ObjectMap = fields
674 .into_iter()
675 .map(|(k, v)| (k.to_string(), v))
676 .collect();
677 ConfigValue::Enum(Sourced::new(EnumValue {
678 variant: variant.to_string(),
679 fields: map,
680 }))
681 }
682
683 fn cv_string(s: &str) -> ConfigValue {
684 ConfigValue::String(Sourced::new(s.to_string()))
685 }
686
687 fn cv_bool(b: bool) -> ConfigValue {
688 ConfigValue::Bool(Sourced::new(b))
689 }
690
691 #[test]
692 fn test_extract_subcommand_path_single_level() {
693 let cli_value = cv_object([(
696 "command",
697 cv_enum("Install", [("package", cv_string("foo"))]),
698 )]);
699
700 let path = cli_value.extract_subcommand_path("command");
701 assert_eq!(path, vec!["Install"]);
702 }
703
704 #[test]
705 fn test_extract_subcommand_path_two_levels() {
706 let cli_value = cv_object([(
709 "command",
710 cv_enum(
711 "Repo",
712 [(
713 "action",
714 cv_enum("Clone", [("url", cv_string("https://example.com"))]),
715 )],
716 ),
717 )]);
718
719 let path = cli_value.extract_subcommand_path("command");
720 assert_eq!(path, vec!["Repo", "Clone"]);
721 }
722
723 #[test]
724 fn test_extract_subcommand_path_three_levels() {
725 let cli_value = cv_object([(
728 "command",
729 cv_enum(
730 "Cloud",
731 [(
732 "provider",
733 cv_enum(
734 "Aws",
735 [(
736 "service",
737 cv_enum("S3", [("action", cv_enum("Upload", []))]),
738 )],
739 ),
740 )],
741 ),
742 )]);
743
744 let path = cli_value.extract_subcommand_path("command");
745 assert_eq!(path, vec!["Cloud", "Aws", "S3", "Upload"]);
746 }
747
748 #[test]
749 fn test_extract_subcommand_path_no_subcommand() {
750 let cli_value = cv_object([("verbose", cv_bool(true))]);
752
753 let path = cli_value.extract_subcommand_path("command");
754 assert!(path.is_empty());
755 }
756
757 #[test]
758 fn test_extract_subcommand_path_with_other_fields() {
759 let cli_value = cv_object([
762 ("verbose", cv_bool(true)),
763 (
764 "command",
765 cv_enum("Install", [("package", cv_string("foo"))]),
766 ),
767 ]);
768
769 let path = cli_value.extract_subcommand_path("command");
770 assert_eq!(path, vec!["Install"]);
771 }
772}