1use styx_parse::{ScalarKind, Separator, 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 separator: Separator,
86 pub span: Option<Span>,
88}
89
90#[derive(Debug, Clone, PartialEq)]
92#[cfg_attr(feature = "facet", derive(facet::Facet))]
93#[cfg_attr(feature = "facet", facet(skip_all_unless_truthy))]
94pub struct Entry {
95 pub key: Value,
97 pub value: Value,
99 pub doc_comment: Option<String>,
101}
102
103impl Value {
104 pub fn unit() -> Self {
106 Value {
107 tag: None,
108 payload: None,
109 span: None,
110 }
111 }
112
113 pub fn scalar(text: impl Into<String>) -> Self {
115 Value {
116 tag: None,
117 payload: Some(Payload::Scalar(Scalar {
118 text: text.into(),
119 kind: ScalarKind::Bare,
120 span: None,
121 })),
122 span: None,
123 }
124 }
125
126 pub fn tag(name: impl Into<String>) -> Self {
128 Value {
129 tag: Some(Tag {
130 name: name.into(),
131 span: None,
132 }),
133 payload: None,
134 span: None,
135 }
136 }
137
138 pub fn tagged(name: impl Into<String>, payload: Value) -> Self {
140 Value {
141 tag: Some(Tag {
142 name: name.into(),
143 span: None,
144 }),
145 payload: payload.payload,
146 span: None,
147 }
148 }
149
150 pub fn sequence() -> Self {
152 Value {
153 tag: None,
154 payload: Some(Payload::Sequence(Sequence {
155 items: Vec::new(),
156 span: None,
157 })),
158 span: None,
159 }
160 }
161
162 pub fn seq(items: Vec<Value>) -> Self {
164 Value {
165 tag: None,
166 payload: Some(Payload::Sequence(Sequence { items, span: None })),
167 span: None,
168 }
169 }
170
171 pub fn object() -> Self {
173 Value {
174 tag: None,
175 payload: Some(Payload::Object(Object {
176 entries: Vec::new(),
177 separator: Separator::Newline,
178 span: None,
179 })),
180 span: None,
181 }
182 }
183
184 pub fn is_unit(&self) -> bool {
186 self.tag.is_none() && self.payload.is_none()
187 }
188
189 pub fn is_schema_tag(&self) -> bool {
191 self.tag_name() == Some("schema")
192 }
193
194 pub fn tag_name(&self) -> Option<&str> {
196 self.tag.as_ref().map(|t| t.name.as_str())
197 }
198
199 pub fn as_str(&self) -> Option<&str> {
201 if self.tag.is_some() {
202 return None;
203 }
204 match &self.payload {
205 Some(Payload::Scalar(s)) => Some(&s.text),
206 _ => None,
207 }
208 }
209
210 pub fn scalar_text(&self) -> Option<&str> {
212 match &self.payload {
213 Some(Payload::Scalar(s)) => Some(&s.text),
214 _ => None,
215 }
216 }
217
218 pub fn as_object(&self) -> Option<&Object> {
220 match &self.payload {
221 Some(Payload::Object(o)) => Some(o),
222 _ => None,
223 }
224 }
225
226 pub fn as_object_mut(&mut self) -> Option<&mut Object> {
228 match &mut self.payload {
229 Some(Payload::Object(o)) => Some(o),
230 _ => None,
231 }
232 }
233
234 pub fn as_sequence(&self) -> Option<&Sequence> {
236 match &self.payload {
237 Some(Payload::Sequence(s)) => Some(s),
238 _ => None,
239 }
240 }
241
242 pub fn as_sequence_mut(&mut self) -> Option<&mut Sequence> {
244 match &mut self.payload {
245 Some(Payload::Sequence(s)) => Some(s),
246 _ => None,
247 }
248 }
249
250 pub fn with_tag(mut self, name: impl Into<String>) -> Self {
252 self.tag = Some(Tag {
253 name: name.into(),
254 span: None,
255 });
256 self
257 }
258
259 pub fn get(&self, path: &str) -> Option<&Value> {
264 if path.is_empty() {
265 return Some(self);
266 }
267
268 let (segment, rest) = split_path(path);
269
270 match &self.payload {
271 Some(Payload::Object(obj)) => {
272 let value = obj.get(segment)?;
273 if rest.is_empty() {
274 Some(value)
275 } else {
276 value.get(rest)
277 }
278 }
279 Some(Payload::Sequence(seq)) => {
280 if segment.starts_with('[') && segment.ends_with(']') {
282 let idx: usize = segment[1..segment.len() - 1].parse().ok()?;
283 let value = seq.get(idx)?;
284 if rest.is_empty() {
285 Some(value)
286 } else {
287 value.get(rest)
288 }
289 } else {
290 None
291 }
292 }
293 _ => None,
294 }
295 }
296
297 pub fn get_mut(&mut self, path: &str) -> Option<&mut Value> {
299 if path.is_empty() {
300 return Some(self);
301 }
302
303 let (segment, rest) = split_path(path);
304
305 match &mut self.payload {
306 Some(Payload::Object(obj)) => {
307 let value = obj.get_mut(segment)?;
308 if rest.is_empty() {
309 Some(value)
310 } else {
311 value.get_mut(rest)
312 }
313 }
314 Some(Payload::Sequence(seq)) => {
315 if segment.starts_with('[') && segment.ends_with(']') {
316 let idx: usize = segment[1..segment.len() - 1].parse().ok()?;
317 let value = seq.get_mut(idx)?;
318 if rest.is_empty() {
319 Some(value)
320 } else {
321 value.get_mut(rest)
322 }
323 } else {
324 None
325 }
326 }
327 _ => None,
328 }
329 }
330}
331
332impl Object {
333 pub fn get(&self, key: &str) -> Option<&Value> {
335 self.entries
336 .iter()
337 .find(|e| e.key.as_str() == Some(key))
338 .map(|e| &e.value)
339 }
340
341 pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
343 self.entries
344 .iter_mut()
345 .find(|e| e.key.as_str() == Some(key))
346 .map(|e| &mut e.value)
347 }
348
349 pub fn get_unit(&self) -> Option<&Value> {
351 self.entries
352 .iter()
353 .find(|e| e.key.is_unit())
354 .map(|e| &e.value)
355 }
356
357 pub fn get_unit_mut(&mut self) -> Option<&mut Value> {
359 self.entries
360 .iter_mut()
361 .find(|e| e.key.is_unit())
362 .map(|e| &mut e.value)
363 }
364
365 pub fn iter(&self) -> impl Iterator<Item = (&Value, &Value)> {
367 self.entries.iter().map(|e| (&e.key, &e.value))
368 }
369
370 pub fn contains_key(&self, key: &str) -> bool {
372 self.entries.iter().any(|e| e.key.as_str() == Some(key))
373 }
374
375 pub fn contains_unit_key(&self) -> bool {
377 self.entries.iter().any(|e| e.key.is_unit())
378 }
379
380 pub fn len(&self) -> usize {
382 self.entries.len()
383 }
384
385 pub fn is_empty(&self) -> bool {
387 self.entries.is_empty()
388 }
389
390 pub fn insert(&mut self, key: impl Into<String>, value: Value) {
392 let key_str = key.into();
393 if let Some(entry) = self
394 .entries
395 .iter_mut()
396 .find(|e| e.key.as_str() == Some(&key_str))
397 {
398 entry.value = value;
399 } else {
400 self.entries.push(Entry {
401 key: Value::scalar(key_str),
402 value,
403 doc_comment: None,
404 });
405 }
406 }
407
408 pub fn insert_unit(&mut self, value: Value) {
410 if let Some(entry) = self.entries.iter_mut().find(|e| e.key.is_unit()) {
411 entry.value = value;
412 } else {
413 self.entries.push(Entry {
414 key: Value::unit(),
415 value,
416 doc_comment: None,
417 });
418 }
419 }
420}
421
422impl Sequence {
423 pub fn get(&self, index: usize) -> Option<&Value> {
425 self.items.get(index)
426 }
427
428 pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> {
430 self.items.get_mut(index)
431 }
432
433 pub fn len(&self) -> usize {
435 self.items.len()
436 }
437
438 pub fn is_empty(&self) -> bool {
440 self.items.is_empty()
441 }
442
443 pub fn iter(&self) -> impl Iterator<Item = &Value> {
445 self.items.iter()
446 }
447
448 pub fn push(&mut self, value: Value) {
450 self.items.push(value);
451 }
452}
453
454fn split_path(path: &str) -> (&str, &str) {
456 if path.starts_with('[')
458 && let Some(end) = path.find(']')
459 {
460 let segment = &path[..=end];
461 let rest = &path[end + 1..];
462 let rest = rest.strip_prefix('.').unwrap_or(rest);
464 return (segment, rest);
465 }
466
467 let dot_pos = path.find('.');
469 let bracket_pos = path.find('[');
470
471 match (dot_pos, bracket_pos) {
472 (Some(d), Some(b)) if b < d => (&path[..b], &path[b..]),
473 (Some(d), _) => (&path[..d], &path[d + 1..]),
474 (None, Some(b)) => (&path[..b], &path[b..]),
475 (None, None) => (path, ""),
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_split_path() {
485 assert_eq!(split_path("foo"), ("foo", ""));
486 assert_eq!(split_path("foo.bar"), ("foo", "bar"));
487 assert_eq!(split_path("foo.bar.baz"), ("foo", "bar.baz"));
488 assert_eq!(split_path("[0]"), ("[0]", ""));
489 assert_eq!(split_path("[0].foo"), ("[0]", "foo"));
490 assert_eq!(split_path("foo[0]"), ("foo", "[0]"));
491 assert_eq!(split_path("foo[0].bar"), ("foo", "[0].bar"));
492 }
493
494 #[test]
495 fn test_unit_value() {
496 let v = Value::unit();
497 assert!(v.is_unit());
498 assert!(v.tag.is_none());
499 assert!(v.payload.is_none());
500 }
501
502 #[test]
503 fn test_scalar_value() {
504 let v = Value::scalar("hello");
505 assert!(!v.is_unit());
506 assert!(v.tag.is_none());
507 assert_eq!(v.as_str(), Some("hello"));
508 }
509
510 #[test]
511 fn test_tagged_value() {
512 let v = Value::tag("string");
513 assert!(!v.is_unit());
514 assert_eq!(v.tag_name(), Some("string"));
515 assert!(v.payload.is_none());
516 }
517
518 #[test]
519 fn test_object_get() {
520 let mut obj = Object {
521 entries: vec![Entry {
522 key: Value::scalar("name"),
523 value: Value::scalar("Alice"),
524 doc_comment: None,
525 }],
526 separator: Separator::Newline,
527 span: None,
528 };
529
530 assert_eq!(obj.get("name").and_then(|v| v.as_str()), Some("Alice"));
531 assert_eq!(obj.get("missing"), None);
532
533 obj.insert("age", Value::scalar("30"));
534 assert_eq!(obj.get("age").and_then(|v| v.as_str()), Some("30"));
535 }
536
537 #[test]
538 fn test_object_unit_key() {
539 let mut obj = Object {
540 entries: vec![],
541 separator: Separator::Newline,
542 span: None,
543 };
544
545 obj.insert_unit(Value::scalar("root"));
546 assert!(obj.contains_unit_key());
547 assert_eq!(obj.get_unit().and_then(|v| v.as_str()), Some("root"));
548 }
549
550 #[test]
551 fn test_value_path_access() {
552 let value = Value {
553 tag: None,
554 payload: Some(Payload::Object(Object {
555 entries: vec![
556 Entry {
557 key: Value::scalar("user"),
558 value: Value {
559 tag: None,
560 payload: Some(Payload::Object(Object {
561 entries: vec![Entry {
562 key: Value::scalar("name"),
563 value: Value::scalar("Alice"),
564 doc_comment: None,
565 }],
566 separator: Separator::Newline,
567 span: None,
568 })),
569 span: None,
570 },
571 doc_comment: None,
572 },
573 Entry {
574 key: Value::scalar("items"),
575 value: Value {
576 tag: None,
577 payload: Some(Payload::Sequence(Sequence {
578 items: vec![
579 Value::scalar("a"),
580 Value::scalar("b"),
581 Value::scalar("c"),
582 ],
583 span: None,
584 })),
585 span: None,
586 },
587 doc_comment: None,
588 },
589 ],
590 separator: Separator::Newline,
591 span: None,
592 })),
593 span: None,
594 };
595
596 assert_eq!(
597 value.get("user.name").and_then(|v| v.as_str()),
598 Some("Alice")
599 );
600 assert_eq!(value.get("items[0]").and_then(|v| v.as_str()), Some("a"));
601 assert_eq!(value.get("items[2]").and_then(|v| v.as_str()), Some("c"));
602 assert_eq!(value.get("missing"), None);
603 }
604
605 #[test]
607 fn test_value_json_roundtrip() {
608 let value = Value {
610 tag: None,
611 payload: Some(Payload::Object(Object {
612 entries: vec![
613 Entry {
615 key: Value::tag("schema"),
616 value: Value::scalar("my-schema.styx"),
617 doc_comment: Some("Schema for this config".to_string()),
618 },
619 Entry {
621 key: Value::scalar("name"),
622 value: Value::scalar("my-app"),
623 doc_comment: None,
624 },
625 Entry {
627 key: Value::scalar("port"),
628 value: Value::tagged("int", Value::scalar("8080")),
629 doc_comment: None,
630 },
631 Entry {
633 key: Value::scalar("server"),
634 value: Value {
635 tag: None,
636 payload: Some(Payload::Object(Object {
637 entries: vec![
638 Entry {
639 key: Value::scalar("host"),
640 value: Value::scalar("localhost"),
641 doc_comment: None,
642 },
643 Entry {
644 key: Value::scalar("tls"),
645 value: Value {
646 tag: Some(Tag {
647 name: "object".to_string(),
648 span: None,
649 }),
650 payload: Some(Payload::Object(Object {
651 entries: vec![
652 Entry {
653 key: Value::scalar("cert"),
654 value: Value::scalar("/path/to/cert.pem"),
655 doc_comment: None,
656 },
657 Entry {
658 key: Value::scalar("key"),
659 value: Value::scalar("/path/to/key.pem"),
660 doc_comment: None,
661 },
662 ],
663 separator: Separator::Comma,
664 span: None,
665 })),
666 span: None,
667 },
668 doc_comment: Some("TLS configuration".to_string()),
669 },
670 ],
671 separator: Separator::Newline,
672 span: None,
673 })),
674 span: None,
675 },
676 doc_comment: Some("Server settings".to_string()),
677 },
678 Entry {
680 key: Value::scalar("tags"),
681 value: Value {
682 tag: None,
683 payload: Some(Payload::Sequence(Sequence {
684 items: vec![
685 Value::scalar("production"),
686 Value::scalar("web"),
687 Value::tagged("important", Value::unit()),
688 ],
689 span: None,
690 })),
691 span: None,
692 },
693 doc_comment: None,
694 },
695 Entry {
697 key: Value::scalar("debug"),
698 value: Value::unit(),
699 doc_comment: None,
700 },
701 ],
702 separator: Separator::Newline,
703 span: Some(Span::new(0, 100)),
704 })),
705 span: Some(Span::new(0, 100)),
706 };
707
708 let json = facet_json::to_string(&value).expect("should serialize");
710 eprintln!("JSON representation:\n{json}");
711
712 let roundtripped: Value = facet_json::from_str(&json).expect("should deserialize");
714
715 assert_eq!(value, roundtripped, "Value should survive JSON roundtrip");
717 }
718
719 #[test]
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 separator: Separator::Newline,
754 span: None,
755 })),
756 span: None,
757 },
758 doc_comment: Some("A nested object".to_string()),
759 },
760 ],
761 separator: Separator::Newline,
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}