1use styx_parse::{ScalarKind, Span};
15
16#[derive(Debug, Clone, PartialEq)]
18#[cfg_attr(feature = "facet", derive(facet::Facet))]
19#[cfg_attr(feature = "facet", facet(skip_all_unless_truthy))]
20pub struct Value {
21 pub tag: Option<Tag>,
23 pub payload: Option<Payload>,
25 pub span: Option<Span>,
27}
28
29#[derive(Debug, Clone, PartialEq)]
31#[cfg_attr(feature = "facet", derive(facet::Facet))]
32#[cfg_attr(feature = "facet", facet(skip_all_unless_truthy))]
33pub struct Tag {
34 pub name: String,
36 pub span: Option<Span>,
38}
39
40#[derive(Debug, Clone, PartialEq)]
42#[cfg_attr(feature = "facet", derive(facet::Facet))]
43#[repr(u8)]
44pub enum Payload {
45 Scalar(Scalar),
47 Sequence(Sequence),
49 Object(Object),
51}
52
53#[derive(Debug, Clone, PartialEq)]
55#[cfg_attr(feature = "facet", derive(facet::Facet))]
56#[cfg_attr(feature = "facet", facet(skip_all_unless_truthy))]
57pub struct Scalar {
58 pub text: String,
60 pub kind: ScalarKind,
62 pub span: Option<Span>,
64}
65
66#[derive(Debug, Clone, PartialEq)]
68#[cfg_attr(feature = "facet", derive(facet::Facet))]
69#[cfg_attr(feature = "facet", facet(skip_all_unless_truthy))]
70pub struct Sequence {
71 pub items: Vec<Value>,
73 pub span: Option<Span>,
75}
76
77#[derive(Debug, Clone, PartialEq)]
79#[cfg_attr(feature = "facet", derive(facet::Facet))]
80#[cfg_attr(feature = "facet", facet(skip_all_unless_truthy))]
81pub struct Object {
82 pub entries: Vec<Entry>,
84 pub span: Option<Span>,
86}
87
88#[derive(Debug, Clone, PartialEq)]
90#[cfg_attr(feature = "facet", derive(facet::Facet))]
91#[cfg_attr(feature = "facet", facet(skip_all_unless_truthy))]
92pub struct Entry {
93 pub key: Value,
95 pub value: Value,
97 pub doc_comment: Option<String>,
99}
100
101impl Value {
102 pub fn unit() -> Self {
104 Value {
105 tag: None,
106 payload: None,
107 span: None,
108 }
109 }
110
111 pub fn scalar(text: impl Into<String>) -> Self {
113 Value {
114 tag: None,
115 payload: Some(Payload::Scalar(Scalar {
116 text: text.into(),
117 kind: ScalarKind::Bare,
118 span: None,
119 })),
120 span: None,
121 }
122 }
123
124 pub fn tag(name: impl Into<String>) -> Self {
126 Value {
127 tag: Some(Tag {
128 name: name.into(),
129 span: None,
130 }),
131 payload: None,
132 span: None,
133 }
134 }
135
136 pub fn tagged(name: impl Into<String>, payload: Value) -> Self {
138 Value {
139 tag: Some(Tag {
140 name: name.into(),
141 span: None,
142 }),
143 payload: payload.payload,
144 span: None,
145 }
146 }
147
148 pub fn sequence() -> Self {
150 Value {
151 tag: None,
152 payload: Some(Payload::Sequence(Sequence {
153 items: Vec::new(),
154 span: None,
155 })),
156 span: None,
157 }
158 }
159
160 pub fn seq(items: Vec<Value>) -> Self {
162 Value {
163 tag: None,
164 payload: Some(Payload::Sequence(Sequence { items, span: None })),
165 span: None,
166 }
167 }
168
169 pub fn object() -> Self {
171 Value {
172 tag: None,
173 payload: Some(Payload::Object(Object {
174 entries: Vec::new(),
175
176 span: None,
177 })),
178 span: None,
179 }
180 }
181
182 pub fn is_unit(&self) -> bool {
184 self.tag.is_none() && self.payload.is_none()
185 }
186
187 pub fn is_schema_tag(&self) -> bool {
189 self.tag_name() == Some("schema")
190 }
191
192 pub fn tag_name(&self) -> Option<&str> {
194 self.tag.as_ref().map(|t| t.name.as_str())
195 }
196
197 pub fn as_str(&self) -> Option<&str> {
199 if self.tag.is_some() {
200 return None;
201 }
202 match &self.payload {
203 Some(Payload::Scalar(s)) => Some(&s.text),
204 _ => None,
205 }
206 }
207
208 pub fn scalar_text(&self) -> Option<&str> {
210 match &self.payload {
211 Some(Payload::Scalar(s)) => Some(&s.text),
212 _ => None,
213 }
214 }
215
216 pub fn as_object(&self) -> Option<&Object> {
218 match &self.payload {
219 Some(Payload::Object(o)) => Some(o),
220 _ => None,
221 }
222 }
223
224 pub fn as_object_mut(&mut self) -> Option<&mut Object> {
226 match &mut self.payload {
227 Some(Payload::Object(o)) => Some(o),
228 _ => None,
229 }
230 }
231
232 pub fn as_sequence(&self) -> Option<&Sequence> {
234 match &self.payload {
235 Some(Payload::Sequence(s)) => Some(s),
236 _ => None,
237 }
238 }
239
240 pub fn as_sequence_mut(&mut self) -> Option<&mut Sequence> {
242 match &mut self.payload {
243 Some(Payload::Sequence(s)) => Some(s),
244 _ => None,
245 }
246 }
247
248 pub fn with_tag(mut self, name: impl Into<String>) -> Self {
250 self.tag = Some(Tag {
251 name: name.into(),
252 span: None,
253 });
254 self
255 }
256
257 pub fn get(&self, path: &str) -> Option<&Value> {
262 if path.is_empty() {
263 return Some(self);
264 }
265
266 let (segment, rest) = split_path(path);
267
268 match &self.payload {
269 Some(Payload::Object(obj)) => {
270 let value = obj.get(segment)?;
271 if rest.is_empty() {
272 Some(value)
273 } else {
274 value.get(rest)
275 }
276 }
277 Some(Payload::Sequence(seq)) => {
278 if segment.starts_with('[') && segment.ends_with(']') {
280 let idx: usize = segment[1..segment.len() - 1].parse().ok()?;
281 let value = seq.get(idx)?;
282 if rest.is_empty() {
283 Some(value)
284 } else {
285 value.get(rest)
286 }
287 } else {
288 None
289 }
290 }
291 _ => None,
292 }
293 }
294
295 pub fn get_mut(&mut self, path: &str) -> Option<&mut Value> {
297 if path.is_empty() {
298 return Some(self);
299 }
300
301 let (segment, rest) = split_path(path);
302
303 match &mut self.payload {
304 Some(Payload::Object(obj)) => {
305 let value = obj.get_mut(segment)?;
306 if rest.is_empty() {
307 Some(value)
308 } else {
309 value.get_mut(rest)
310 }
311 }
312 Some(Payload::Sequence(seq)) => {
313 if segment.starts_with('[') && segment.ends_with(']') {
314 let idx: usize = segment[1..segment.len() - 1].parse().ok()?;
315 let value = seq.get_mut(idx)?;
316 if rest.is_empty() {
317 Some(value)
318 } else {
319 value.get_mut(rest)
320 }
321 } else {
322 None
323 }
324 }
325 _ => None,
326 }
327 }
328}
329
330impl Object {
331 pub fn get(&self, key: &str) -> Option<&Value> {
333 self.entries
334 .iter()
335 .find(|e| e.key.as_str() == Some(key))
336 .map(|e| &e.value)
337 }
338
339 pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
341 self.entries
342 .iter_mut()
343 .find(|e| e.key.as_str() == Some(key))
344 .map(|e| &mut e.value)
345 }
346
347 pub fn get_unit(&self) -> Option<&Value> {
349 self.entries
350 .iter()
351 .find(|e| e.key.is_unit())
352 .map(|e| &e.value)
353 }
354
355 pub fn get_unit_mut(&mut self) -> Option<&mut Value> {
357 self.entries
358 .iter_mut()
359 .find(|e| e.key.is_unit())
360 .map(|e| &mut e.value)
361 }
362
363 pub fn iter(&self) -> impl Iterator<Item = (&Value, &Value)> {
365 self.entries.iter().map(|e| (&e.key, &e.value))
366 }
367
368 pub fn contains_key(&self, key: &str) -> bool {
370 self.entries.iter().any(|e| e.key.as_str() == Some(key))
371 }
372
373 pub fn contains_unit_key(&self) -> bool {
375 self.entries.iter().any(|e| e.key.is_unit())
376 }
377
378 pub fn len(&self) -> usize {
380 self.entries.len()
381 }
382
383 pub fn is_empty(&self) -> bool {
385 self.entries.is_empty()
386 }
387
388 pub fn insert(&mut self, key: impl Into<String>, value: Value) {
390 let key_str = key.into();
391 if let Some(entry) = self
392 .entries
393 .iter_mut()
394 .find(|e| e.key.as_str() == Some(&key_str))
395 {
396 entry.value = value;
397 } else {
398 self.entries.push(Entry {
399 key: Value::scalar(key_str),
400 value,
401 doc_comment: None,
402 });
403 }
404 }
405
406 pub fn insert_unit(&mut self, value: Value) {
408 if let Some(entry) = self.entries.iter_mut().find(|e| e.key.is_unit()) {
409 entry.value = value;
410 } else {
411 self.entries.push(Entry {
412 key: Value::unit(),
413 value,
414 doc_comment: None,
415 });
416 }
417 }
418}
419
420impl Sequence {
421 pub fn get(&self, index: usize) -> Option<&Value> {
423 self.items.get(index)
424 }
425
426 pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> {
428 self.items.get_mut(index)
429 }
430
431 pub fn len(&self) -> usize {
433 self.items.len()
434 }
435
436 pub fn is_empty(&self) -> bool {
438 self.items.is_empty()
439 }
440
441 pub fn iter(&self) -> impl Iterator<Item = &Value> {
443 self.items.iter()
444 }
445
446 pub fn push(&mut self, value: Value) {
448 self.items.push(value);
449 }
450}
451
452fn split_path(path: &str) -> (&str, &str) {
454 if path.starts_with('[')
456 && let Some(end) = path.find(']')
457 {
458 let segment = &path[..=end];
459 let rest = &path[end + 1..];
460 let rest = rest.strip_prefix('.').unwrap_or(rest);
462 return (segment, rest);
463 }
464
465 let dot_pos = path.find('.');
467 let bracket_pos = path.find('[');
468
469 match (dot_pos, bracket_pos) {
470 (Some(d), Some(b)) if b < d => (&path[..b], &path[b..]),
471 (Some(d), _) => (&path[..d], &path[d + 1..]),
472 (None, Some(b)) => (&path[..b], &path[b..]),
473 (None, None) => (path, ""),
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn test_split_path() {
483 assert_eq!(split_path("foo"), ("foo", ""));
484 assert_eq!(split_path("foo.bar"), ("foo", "bar"));
485 assert_eq!(split_path("foo.bar.baz"), ("foo", "bar.baz"));
486 assert_eq!(split_path("[0]"), ("[0]", ""));
487 assert_eq!(split_path("[0].foo"), ("[0]", "foo"));
488 assert_eq!(split_path("foo[0]"), ("foo", "[0]"));
489 assert_eq!(split_path("foo[0].bar"), ("foo", "[0].bar"));
490 }
491
492 #[test]
493 fn test_unit_value() {
494 let v = Value::unit();
495 assert!(v.is_unit());
496 assert!(v.tag.is_none());
497 assert!(v.payload.is_none());
498 }
499
500 #[test]
501 fn test_scalar_value() {
502 let v = Value::scalar("hello");
503 assert!(!v.is_unit());
504 assert!(v.tag.is_none());
505 assert_eq!(v.as_str(), Some("hello"));
506 }
507
508 #[test]
509 fn test_tagged_value() {
510 let v = Value::tag("string");
511 assert!(!v.is_unit());
512 assert_eq!(v.tag_name(), Some("string"));
513 assert!(v.payload.is_none());
514 }
515
516 #[test]
517 fn test_object_get() {
518 let mut obj = Object {
519 entries: vec![Entry {
520 key: Value::scalar("name"),
521 value: Value::scalar("Alice"),
522 doc_comment: None,
523 }],
524
525 span: None,
526 };
527
528 assert_eq!(obj.get("name").and_then(|v| v.as_str()), Some("Alice"));
529 assert_eq!(obj.get("missing"), None);
530
531 obj.insert("age", Value::scalar("30"));
532 assert_eq!(obj.get("age").and_then(|v| v.as_str()), Some("30"));
533 }
534
535 #[test]
536 fn test_object_unit_key() {
537 let mut obj = Object {
538 entries: vec![],
539
540 span: None,
541 };
542
543 obj.insert_unit(Value::scalar("root"));
544 assert!(obj.contains_unit_key());
545 assert_eq!(obj.get_unit().and_then(|v| v.as_str()), Some("root"));
546 }
547
548 #[test]
549 fn test_value_path_access() {
550 let value = Value {
551 tag: None,
552 payload: Some(Payload::Object(Object {
553 entries: vec![
554 Entry {
555 key: Value::scalar("user"),
556 value: Value {
557 tag: None,
558 payload: Some(Payload::Object(Object {
559 entries: vec![Entry {
560 key: Value::scalar("name"),
561 value: Value::scalar("Alice"),
562 doc_comment: None,
563 }],
564
565 span: None,
566 })),
567 span: None,
568 },
569 doc_comment: None,
570 },
571 Entry {
572 key: Value::scalar("items"),
573 value: Value {
574 tag: None,
575 payload: Some(Payload::Sequence(Sequence {
576 items: vec![
577 Value::scalar("a"),
578 Value::scalar("b"),
579 Value::scalar("c"),
580 ],
581 span: None,
582 })),
583 span: None,
584 },
585 doc_comment: None,
586 },
587 ],
588
589 span: None,
590 })),
591 span: None,
592 };
593
594 assert_eq!(
595 value.get("user.name").and_then(|v| v.as_str()),
596 Some("Alice")
597 );
598 assert_eq!(value.get("items[0]").and_then(|v| v.as_str()), Some("a"));
599 assert_eq!(value.get("items[2]").and_then(|v| v.as_str()), Some("c"));
600 assert_eq!(value.get("missing"), None);
601 }
602
603 #[test]
605 #[cfg(feature = "facet")]
606 fn test_value_json_roundtrip() {
607 let value = Value {
609 tag: None,
610 payload: Some(Payload::Object(Object {
611 entries: vec![
612 Entry {
614 key: Value::tag("schema"),
615 value: Value::scalar("my-schema.styx"),
616 doc_comment: Some("Schema for this config".to_string()),
617 },
618 Entry {
620 key: Value::scalar("name"),
621 value: Value::scalar("my-app"),
622 doc_comment: None,
623 },
624 Entry {
626 key: Value::scalar("port"),
627 value: Value::tagged("int", Value::scalar("8080")),
628 doc_comment: None,
629 },
630 Entry {
632 key: Value::scalar("server"),
633 value: Value {
634 tag: None,
635 payload: Some(Payload::Object(Object {
636 entries: vec![
637 Entry {
638 key: Value::scalar("host"),
639 value: Value::scalar("localhost"),
640 doc_comment: None,
641 },
642 Entry {
643 key: Value::scalar("tls"),
644 value: Value {
645 tag: Some(Tag {
646 name: "object".to_string(),
647 span: None,
648 }),
649 payload: Some(Payload::Object(Object {
650 entries: vec![
651 Entry {
652 key: Value::scalar("cert"),
653 value: Value::scalar("/path/to/cert.pem"),
654 doc_comment: None,
655 },
656 Entry {
657 key: Value::scalar("key"),
658 value: Value::scalar("/path/to/key.pem"),
659 doc_comment: None,
660 },
661 ],
662
663 span: None,
664 })),
665 span: None,
666 },
667 doc_comment: Some("TLS configuration".to_string()),
668 },
669 ],
670
671 span: None,
672 })),
673 span: None,
674 },
675 doc_comment: Some("Server settings".to_string()),
676 },
677 Entry {
679 key: Value::scalar("tags"),
680 value: Value {
681 tag: None,
682 payload: Some(Payload::Sequence(Sequence {
683 items: vec![
684 Value::scalar("production"),
685 Value::scalar("web"),
686 Value::tagged("important", Value::unit()),
687 ],
688 span: None,
689 })),
690 span: None,
691 },
692 doc_comment: None,
693 },
694 Entry {
696 key: Value::scalar("debug"),
697 value: Value::unit(),
698 doc_comment: None,
699 },
700 ],
701
702 span: Some(Span::new(0, 100)),
703 })),
704 span: Some(Span::new(0, 100)),
705 };
706
707 let json = facet_json::to_string(&value).expect("should serialize");
709 eprintln!("JSON representation:\n{json}");
710
711 let roundtripped: Value = facet_json::from_str(&json).expect("should deserialize");
713
714 assert_eq!(value, roundtripped, "Value should survive JSON roundtrip");
716 }
717
718 #[test]
719 #[cfg(feature = "facet")]
720 fn test_value_postcard_roundtrip() {
721 let v = Value::scalar("hello");
723 let bytes = facet_postcard::to_vec(&v).expect("serialize scalar");
724 let v2: Value = facet_postcard::from_slice(&bytes).expect("deserialize scalar");
725 assert_eq!(v, v2);
726
727 let v = Value::tag("string");
729 let bytes = facet_postcard::to_vec(&v).expect("serialize tagged");
730 let v2: Value = facet_postcard::from_slice(&bytes).expect("deserialize tagged");
731 assert_eq!(v, v2);
732
733 let v = Value {
735 tag: None,
736 payload: Some(Payload::Object(Object {
737 entries: vec![
738 Entry {
739 key: Value::scalar("name"),
740 value: Value::scalar("Alice"),
741 doc_comment: None,
742 },
743 Entry {
744 key: Value::scalar("nested"),
745 value: Value {
746 tag: None,
747 payload: Some(Payload::Object(Object {
748 entries: vec![Entry {
749 key: Value::scalar("inner"),
750 value: Value::scalar("value"),
751 doc_comment: None,
752 }],
753
754 span: None,
755 })),
756 span: None,
757 },
758 doc_comment: Some("A nested object".to_string()),
759 },
760 ],
761
762 span: None,
763 })),
764 span: None,
765 };
766 let bytes = facet_postcard::to_vec(&v).expect("serialize nested");
767 let v2: Value = facet_postcard::from_slice(&bytes).expect("deserialize nested");
768 assert_eq!(v, v2);
769
770 let v = Value::seq(vec![
772 Value::scalar("a"),
773 Value::scalar("b"),
774 Value::tagged("important", Value::unit()),
775 ]);
776 let bytes = facet_postcard::to_vec(&v).expect("serialize sequence");
777 let v2: Value = facet_postcard::from_slice(&bytes).expect("deserialize sequence");
778 assert_eq!(v, v2);
779 }
780}