1use std::borrow::Cow;
4
5use crate::trace;
6use facet_core::Facet;
7use facet_format::{
8 FieldKey, FieldLocationHint, FormatSerializer, ScalarValue, SerializeError, serialize_root,
9};
10use facet_reflect::{HasFields, Peek};
11use styx_format::{FormatOptions, StyxWriter};
12
13pub use styx_format::FormatOptions as SerializeOptions;
15
16fn extract_field_key<'mem, 'facet>(key: Peek<'mem, 'facet>) -> Option<FieldKey<'mem>> {
21 if key.shape().is_metadata_container()
23 && let Ok(container) = key.into_struct()
24 {
25 let mut doc_lines: Vec<Cow<'mem, str>> = Vec::new();
26 let mut tag_value: Option<Cow<'mem, str>> = None;
27 let mut name_value: Option<Cow<'mem, str>> = None;
28
29 for (f, field_value) in container.fields() {
30 if f.metadata_kind() == Some("doc") {
31 if let Ok(opt) = field_value.into_option()
33 && let Some(inner) = opt.value()
34 && let Ok(list) = inner.into_list_like()
35 {
36 for item in list.iter() {
37 if let Some(line) = item.as_str() {
38 doc_lines.push(Cow::Borrowed(line));
39 }
40 }
41 }
42 } else if f.metadata_kind() == Some("tag") {
43 if let Ok(opt) = field_value.into_option()
45 && let Some(inner) = opt.value()
46 && let Some(s) = inner.as_str()
47 {
48 tag_value = Some(Cow::Borrowed(s));
49 }
50 } else if f.metadata_kind().is_none() {
51 let (inner_name, inner_tag) = extract_name_and_tag(field_value);
53 if inner_name.is_some() {
54 name_value = inner_name;
55 }
56 if inner_tag.is_some() {
57 tag_value = inner_tag;
58 }
59 }
60 }
61
62 return Some(match (name_value, tag_value) {
64 (Some(name), Some(tag)) => {
65 FieldKey::tagged_with_name_and_doc(
67 tag,
68 name,
69 FieldLocationHint::KeyValue,
70 doc_lines,
71 )
72 }
73 (Some(name), None) => {
74 FieldKey::with_doc(name, FieldLocationHint::KeyValue, doc_lines)
76 }
77 (None, Some(tag)) => {
78 FieldKey::tagged_with_doc(tag, FieldLocationHint::KeyValue, doc_lines)
80 }
81 (None, None) => {
82 FieldKey::unit_with_doc(FieldLocationHint::KeyValue, doc_lines)
84 }
85 });
86 }
87
88 if let Ok(opt) = key.into_option() {
90 return match opt.value() {
91 Some(inner) => inner
92 .as_str()
93 .map(|s| FieldKey::new(s, FieldLocationHint::KeyValue)),
94 None => {
95 Some(FieldKey::unit(FieldLocationHint::KeyValue))
97 }
98 };
99 }
100
101 if let Some(s) = key.as_str() {
103 return Some(FieldKey::new(s, FieldLocationHint::KeyValue));
104 }
105
106 None
107}
108
109fn extract_name_and_tag<'mem, 'facet>(
111 value: Peek<'mem, 'facet>,
112) -> (Option<Cow<'mem, str>>, Option<Cow<'mem, str>>) {
113 if let Some(s) = value.as_str() {
115 return (Some(Cow::Borrowed(s)), None);
116 }
117
118 if let Ok(opt) = value.into_option() {
120 return match opt.value() {
121 Some(inner) => {
122 if let Some(s) = inner.as_str() {
123 (Some(Cow::Borrowed(s)), None)
124 } else {
125 (None, None)
126 }
127 }
128 None => (None, None),
129 };
130 }
131
132 if value.shape().is_metadata_container()
134 && let Ok(container) = value.into_struct()
135 {
136 let mut name: Option<Cow<'mem, str>> = None;
137 let mut tag: Option<Cow<'mem, str>> = None;
138
139 for (f, field_value) in container.fields() {
140 if f.metadata_kind() == Some("tag") {
141 if let Ok(opt) = field_value.into_option()
142 && let Some(inner) = opt.value()
143 && let Some(s) = inner.as_str()
144 {
145 tag = Some(Cow::Borrowed(s));
146 }
147 } else if f.metadata_kind().is_none() {
148 if let Some(s) = field_value.as_str() {
150 name = Some(Cow::Borrowed(s));
151 } else if let Ok(opt) = field_value.into_option()
152 && let Some(inner) = opt.value()
153 && let Some(s) = inner.as_str()
154 {
155 name = Some(Cow::Borrowed(s));
156 }
157 }
158 }
159
160 return (name, tag);
161 }
162
163 (None, None)
164}
165
166#[derive(Debug)]
168pub struct StyxSerializeError {
169 msg: Cow<'static, str>,
170}
171
172impl StyxSerializeError {
173 fn new(msg: impl Into<Cow<'static, str>>) -> Self {
174 Self { msg: msg.into() }
175 }
176}
177
178impl core::fmt::Display for StyxSerializeError {
179 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
180 f.write_str(&self.msg)
181 }
182}
183
184impl std::error::Error for StyxSerializeError {}
185
186pub struct StyxSerializer {
188 writer: StyxWriter,
189 at_root: bool,
191 just_wrote_tag: bool,
193}
194
195impl StyxSerializer {
196 pub fn new() -> Self {
198 Self::with_options(FormatOptions::default())
199 }
200
201 pub fn with_options(options: FormatOptions) -> Self {
203 Self {
204 writer: StyxWriter::with_options(options),
205 at_root: true,
206 just_wrote_tag: false,
207 }
208 }
209
210 pub fn finish(self) -> Vec<u8> {
212 self.writer.finish_document()
213 }
214}
215
216impl Default for StyxSerializer {
217 fn default() -> Self {
218 Self::new()
219 }
220}
221
222impl FormatSerializer for StyxSerializer {
223 type Error = StyxSerializeError;
224
225 fn begin_struct(&mut self) -> Result<(), Self::Error> {
226 let is_root = self.at_root;
227 trace!(is_root, "begin_struct");
228 self.at_root = false;
229 self.just_wrote_tag = false;
230 self.writer.clear_skip_before_value();
231 self.writer.begin_struct(is_root);
232 Ok(())
233 }
234
235 fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
236 trace!(key, "field_key");
237 self.just_wrote_tag = false;
238 self.writer.clear_skip_before_value();
239 self.writer.field_key(key).map_err(StyxSerializeError::new)
240 }
241
242 fn emit_field_key(&mut self, key: &facet_format::FieldKey<'_>) -> Result<(), Self::Error> {
243 trace!(?key, "emit_field_key");
244 self.just_wrote_tag = false;
245 self.writer.clear_skip_before_value();
246
247 let doc_lines: Vec<&str> = key
248 .doc()
249 .map(|d| d.iter().map(|s| s.as_ref()).collect())
250 .unwrap_or_default();
251
252 match (
258 key.tag().map(|c| c.as_ref()),
259 key.name().map(|c| c.as_ref()),
260 ) {
261 (Some(tag), Some(name)) => {
262 let key_str = if tag.is_empty() {
264 format!("@\"{}\"", name)
265 } else {
266 format!("@{}\"{}\"", tag, name)
267 };
268 if !doc_lines.is_empty() {
269 self.writer
270 .write_doc_comment_and_key_raw(&doc_lines.join("\n"), &key_str);
271 } else {
272 self.writer
273 .field_key_raw(&key_str)
274 .map_err(StyxSerializeError::new)?;
275 }
276 }
277 (Some(tag), None) => {
278 let key_str = if tag.is_empty() {
280 "@".to_string()
281 } else {
282 format!("@{}", tag)
283 };
284 if !doc_lines.is_empty() {
285 self.writer
286 .write_doc_comment_and_key_raw(&doc_lines.join("\n"), &key_str);
287 } else {
288 self.writer
289 .field_key_raw(&key_str)
290 .map_err(StyxSerializeError::new)?;
291 }
292 }
293 (None, Some(name)) => {
294 if !doc_lines.is_empty() {
296 self.writer
297 .write_doc_comment_and_key(&doc_lines.join("\n"), name);
298 } else {
299 self.writer
300 .field_key(name)
301 .map_err(StyxSerializeError::new)?;
302 }
303 }
304 (None, None) => {
305 if !doc_lines.is_empty() {
307 self.writer
308 .write_doc_comment_and_key_raw(&doc_lines.join("\n"), "@");
309 } else {
310 self.writer
311 .field_key_raw("@")
312 .map_err(StyxSerializeError::new)?;
313 }
314 }
315 }
316 Ok(())
317 }
318
319 fn end_struct(&mut self) -> Result<(), Self::Error> {
320 trace!("end_struct");
321 self.writer.end_struct().map_err(StyxSerializeError::new)
322 }
323
324 fn begin_seq(&mut self) -> Result<(), Self::Error> {
325 trace!("begin_seq");
326 self.at_root = false;
327 self.just_wrote_tag = false;
328 self.writer.clear_skip_before_value();
329 self.writer.begin_seq();
330 Ok(())
331 }
332
333 fn end_seq(&mut self) -> Result<(), Self::Error> {
334 trace!("end_seq");
335 self.writer.end_seq().map_err(StyxSerializeError::new)
336 }
337
338 fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
339 trace!(?scalar, just_wrote_tag = self.just_wrote_tag, "scalar");
340 self.at_root = false;
341 if self.just_wrote_tag && matches!(scalar, ScalarValue::Unit | ScalarValue::Null) {
343 self.just_wrote_tag = false;
344 self.writer.clear_skip_before_value();
345 return Ok(());
346 }
347 self.just_wrote_tag = false;
348 match scalar {
349 ScalarValue::Unit | ScalarValue::Null => self.writer.write_null(),
350 ScalarValue::Bool(v) => self.writer.write_bool(v),
351 ScalarValue::Char(c) => self.writer.write_char(c),
352 ScalarValue::I64(v) => self.writer.write_i64(v),
353 ScalarValue::U64(v) => self.writer.write_u64(v),
354 ScalarValue::I128(v) => self.writer.write_i128(v),
355 ScalarValue::U128(v) => self.writer.write_u128(v),
356 ScalarValue::F64(v) => self.writer.write_f64(v),
357 ScalarValue::Str(s) => self.writer.write_string(&s),
358 ScalarValue::Bytes(bytes) => self.writer.write_bytes(&bytes),
359 }
360 Ok(())
361 }
362
363 fn serialize_none(&mut self) -> Result<(), Self::Error> {
364 trace!(just_wrote_tag = self.just_wrote_tag, "serialize_none");
365 if self.just_wrote_tag {
367 self.just_wrote_tag = false;
368 self.writer.clear_skip_before_value();
370 return Ok(());
371 }
372 self.at_root = false;
373 self.writer.write_null();
374 Ok(())
375 }
376
377 fn write_variant_tag(&mut self, variant_name: &str) -> Result<bool, Self::Error> {
378 trace!(variant_name, "write_variant_tag");
379 self.at_root = false;
380 if self.just_wrote_tag {
381 self.writer.write_tag_chain_segment(variant_name);
382 } else {
383 self.writer.write_tag(variant_name);
384 }
385 self.just_wrote_tag = true;
386 Ok(true)
387 }
388
389 fn begin_struct_after_tag(&mut self) -> Result<(), Self::Error> {
390 trace!("begin_struct_after_tag");
391 self.just_wrote_tag = false;
392 self.writer.begin_struct_after_tag(false);
393 Ok(())
394 }
395
396 fn begin_seq_after_tag(&mut self) -> Result<(), Self::Error> {
397 trace!("begin_seq_after_tag");
398 self.just_wrote_tag = false;
399 self.writer.begin_seq_after_tag();
400 Ok(())
401 }
402
403 fn finish_variant_tag_unit_payload(&mut self) -> Result<(), Self::Error> {
404 trace!("finish_variant_tag_unit_payload");
405 self.just_wrote_tag = false;
408 self.writer.clear_skip_before_value();
409 Ok(())
410 }
411
412 fn raw_serialize_shape(&self) -> Option<&'static facet_core::Shape> {
413 Some(crate::RawStyx::SHAPE)
414 }
415
416 fn raw_scalar(&mut self, content: &str) -> Result<(), Self::Error> {
417 trace!(content, "raw_scalar");
418 self.at_root = false;
420 self.just_wrote_tag = false;
421 self.writer.before_value();
422 self.writer.write_str(content);
423 Ok(())
424 }
425
426 fn serialize_map_key(&mut self, key: Peek<'_, '_>) -> Result<bool, Self::Error> {
427 trace!(shape = key.shape().type_identifier, "serialize_map_key");
428
429 if let Some(field_key) = extract_field_key(key) {
431 trace!(?field_key, "serialize_map_key: extracted FieldKey");
432 self.emit_field_key(&field_key)?;
433 return Ok(true);
434 }
435
436 trace!("serialize_map_key: falling back to default");
438 Ok(false)
439 }
440
441 fn serialize_metadata_container(
442 &mut self,
443 container: &facet_reflect::PeekStruct<'_, '_>,
444 ) -> Result<bool, Self::Error> {
445 trace!("serialize_metadata_container");
446
447 for (f, field_value) in container.fields() {
449 if f.metadata_kind() == Some("tag") {
450 if let Ok(opt) = field_value.into_option()
452 && let Some(inner) = opt.value()
453 && let Some(s) = inner.as_str()
454 {
455 self.write_variant_tag(s)?;
457 }
458 break;
459 }
460 }
461
462 Ok(false)
465 }
466
467 fn field_metadata_with_value(
468 &mut self,
469 field_item: &facet_reflect::FieldItem,
470 value: Peek<'_, '_>,
471 ) -> Result<bool, Self::Error> {
472 let is_metadata_container = value.shape().is_metadata_container();
473 trace!(
474 field_name = field_item.effective_name(),
475 is_metadata_container,
476 value_shape = value.shape().type_identifier,
477 "field_metadata_with_value"
478 );
479
480 if is_metadata_container && let Ok(container) = value.into_struct() {
483 let mut doc_lines: Vec<&str> = Vec::new();
485 for (f, field_value) in container.fields() {
486 trace!(
487 metadata_kind = ?f.metadata_kind(),
488 field = f.effective_name(),
489 "field_metadata_with_value: inspecting container field"
490 );
491 if f.metadata_kind() == Some("doc")
492 && let Ok(opt) = field_value.into_option()
493 && let Some(inner) = opt.value()
494 && let Ok(list) = inner.into_list_like()
495 {
496 for item in list.iter() {
497 if let Some(line) = item.as_str() {
498 doc_lines.push(line);
499 }
500 }
501 }
502 }
503
504 if !doc_lines.is_empty() {
506 trace!(doc_lines = ?doc_lines, "field_metadata_with_value: emitting doc comment");
507 let doc = doc_lines.join("\n");
508 self.writer
509 .write_doc_comment_and_key(&doc, field_item.effective_name());
510 return Ok(true);
511 }
512 }
513
514 Ok(false)
520 }
521}
522
523pub fn to_string<'facet, T>(value: &T) -> Result<String, SerializeError<StyxSerializeError>>
547where
548 T: Facet<'facet> + ?Sized,
549{
550 to_string_with_options(value, &FormatOptions::default())
551}
552
553pub fn to_string_compact<'facet, T>(value: &T) -> Result<String, SerializeError<StyxSerializeError>>
569where
570 T: Facet<'facet> + ?Sized,
571{
572 let options = FormatOptions::default().inline();
574 let mut serializer = CompactStyxSerializer::with_options(options);
575 serialize_root(&mut serializer, Peek::new(value))?;
576 let bytes = serializer.finish();
577 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
578}
579
580pub fn to_string_with_options<'facet, T>(
582 value: &T,
583 options: &FormatOptions,
584) -> Result<String, SerializeError<StyxSerializeError>>
585where
586 T: Facet<'facet> + ?Sized,
587{
588 let mut serializer = StyxSerializer::with_options(options.clone());
589 serialize_root(&mut serializer, Peek::new(value))?;
590 let bytes = serializer.finish();
591 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
592}
593
594pub fn peek_to_string<'input, 'facet>(
596 peek: Peek<'input, 'facet>,
597) -> Result<String, SerializeError<StyxSerializeError>> {
598 peek_to_string_with_options(peek, &FormatOptions::default())
599}
600
601pub fn peek_to_string_with_options<'input, 'facet>(
603 peek: Peek<'input, 'facet>,
604 options: &FormatOptions,
605) -> Result<String, SerializeError<StyxSerializeError>> {
606 let mut serializer = StyxSerializer::with_options(options.clone());
607 serialize_root(&mut serializer, peek)?;
608 let bytes = serializer.finish();
609 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
610}
611
612pub fn peek_to_string_expr<'input, 'facet>(
617 peek: Peek<'input, 'facet>,
618) -> Result<String, SerializeError<StyxSerializeError>> {
619 let options = FormatOptions::default().inline();
620 let mut serializer = CompactStyxSerializer::with_options(options);
621 serialize_root(&mut serializer, peek)?;
622 let bytes = serializer.finish();
623 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
624}
625
626struct CompactStyxSerializer {
632 writer: StyxWriter,
633 just_wrote_tag: bool,
634}
635
636impl CompactStyxSerializer {
637 fn with_options(options: FormatOptions) -> Self {
638 Self {
639 writer: StyxWriter::with_options(options),
640 just_wrote_tag: false,
641 }
642 }
643
644 fn finish(self) -> Vec<u8> {
645 self.writer.finish()
647 }
648}
649
650impl FormatSerializer for CompactStyxSerializer {
651 type Error = StyxSerializeError;
652
653 fn begin_struct(&mut self) -> Result<(), Self::Error> {
654 self.just_wrote_tag = false;
656 self.writer.clear_skip_before_value();
657 self.writer.begin_struct(false);
658 Ok(())
659 }
660
661 fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
662 self.just_wrote_tag = false;
663 self.writer.clear_skip_before_value();
664 self.writer.field_key(key).map_err(StyxSerializeError::new)
665 }
666
667 fn end_struct(&mut self) -> Result<(), Self::Error> {
668 self.writer.end_struct().map_err(StyxSerializeError::new)
669 }
670
671 fn begin_seq(&mut self) -> Result<(), Self::Error> {
672 self.just_wrote_tag = false;
673 self.writer.clear_skip_before_value();
674 self.writer.begin_seq();
675 Ok(())
676 }
677
678 fn end_seq(&mut self) -> Result<(), Self::Error> {
679 self.writer.end_seq().map_err(StyxSerializeError::new)
680 }
681
682 fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
683 if self.just_wrote_tag && matches!(scalar, ScalarValue::Unit | ScalarValue::Null) {
684 self.just_wrote_tag = false;
685 self.writer.clear_skip_before_value();
686 return Ok(());
687 }
688 self.just_wrote_tag = false;
689 match scalar {
690 ScalarValue::Unit | ScalarValue::Null => self.writer.write_null(),
691 ScalarValue::Bool(v) => self.writer.write_bool(v),
692 ScalarValue::Char(c) => self.writer.write_char(c),
693 ScalarValue::I64(v) => self.writer.write_i64(v),
694 ScalarValue::U64(v) => self.writer.write_u64(v),
695 ScalarValue::I128(v) => self.writer.write_i128(v),
696 ScalarValue::U128(v) => self.writer.write_u128(v),
697 ScalarValue::F64(v) => self.writer.write_f64(v),
698 ScalarValue::Str(s) => self.writer.write_string(&s),
699 ScalarValue::Bytes(bytes) => self.writer.write_bytes(&bytes),
700 }
701 Ok(())
702 }
703
704 fn serialize_none(&mut self) -> Result<(), Self::Error> {
705 if self.just_wrote_tag {
706 self.just_wrote_tag = false;
707 self.writer.clear_skip_before_value();
708 return Ok(());
709 }
710 self.writer.write_null();
711 Ok(())
712 }
713
714 fn write_variant_tag(&mut self, variant_name: &str) -> Result<bool, Self::Error> {
715 if self.just_wrote_tag {
716 self.writer.write_tag_chain_segment(variant_name);
717 } else {
718 self.writer.write_tag(variant_name);
719 }
720 self.just_wrote_tag = true;
721 Ok(true)
722 }
723
724 fn begin_struct_after_tag(&mut self) -> Result<(), Self::Error> {
725 self.just_wrote_tag = false;
726 self.writer.begin_struct_after_tag(false);
727 Ok(())
728 }
729
730 fn begin_seq_after_tag(&mut self) -> Result<(), Self::Error> {
731 self.just_wrote_tag = false;
732 self.writer.begin_seq_after_tag();
733 Ok(())
734 }
735
736 fn finish_variant_tag_unit_payload(&mut self) -> Result<(), Self::Error> {
737 self.just_wrote_tag = false;
738 self.writer.clear_skip_before_value();
739 Ok(())
740 }
741}
742
743#[cfg(test)]
744mod tests {
745 use super::*;
746 use facet::Facet;
747 use facet_testhelpers::test;
748
749 #[derive(Facet, Debug)]
750 struct Simple {
751 name: String,
752 value: i32,
753 }
754
755 #[derive(Facet, Debug)]
756 struct Nested {
757 inner: Simple,
758 }
759
760 #[derive(Facet, Debug)]
761 struct WithVec {
762 items: Vec<i32>,
763 }
764
765 #[derive(Facet, Debug)]
766 struct WithOptional {
767 required: String,
768 optional: Option<i32>,
769 }
770
771 #[test]
772 fn test_simple_struct() {
773 let value = Simple {
774 name: "hello".into(),
775 value: 42,
776 };
777 let result = to_string(&value).unwrap();
778 assert!(result.contains("name hello"));
779 assert!(result.contains("value 42"));
780 }
781
782 #[test]
783 fn test_compact_struct() {
784 let value = Simple {
785 name: "hello".into(),
786 value: 42,
787 };
788 let result = to_string_compact(&value).unwrap();
789 assert_eq!(result, "{name hello, value 42}");
790 }
791
792 #[test]
793 fn test_nested_struct() {
794 let value = Nested {
795 inner: Simple {
796 name: "test".into(),
797 value: 123,
798 },
799 };
800 let result = to_string(&value).unwrap();
801 assert!(result.contains("inner"));
802 assert!(result.contains("{name test, value 123}"));
804 }
805
806 #[test]
807 fn test_sequence() {
808 let value = WithVec {
809 items: vec![1, 2, 3, 4, 5],
810 };
811 let result = to_string(&value).unwrap();
812 assert!(result.contains("items (1 2 3 4 5)"));
813 }
814
815 #[test]
816 fn test_quoted_string() {
817 let value = Simple {
818 name: "hello world".into(), value: 42,
820 };
821 let result = to_string(&value).unwrap();
822 assert!(result.contains("name \"hello world\""));
823 }
824
825 #[test]
826 fn test_special_chars_need_quoting() {
827 let value = Simple {
828 name: "{braces}".into(),
829 value: 42,
830 };
831 let result = to_string(&value).unwrap();
832 assert!(result.contains("name \"{braces}\""));
833 }
834
835 #[test]
836 fn test_optional_none() {
837 let value = WithOptional {
838 required: "hello".into(),
839 optional: None,
840 };
841 let result = to_string(&value).unwrap();
842 assert!(result.contains("required hello"));
843 assert!(result.contains("optional @"));
845 }
846
847 #[test]
848 fn test_optional_some() {
849 let value = WithOptional {
850 required: "hello".into(),
851 optional: Some(42),
852 };
853 let result = to_string(&value).unwrap();
854 assert!(result.contains("required hello"));
855 assert!(result.contains("optional 42"));
856 }
857
858 #[test]
859 fn test_bool_values() {
860 #[derive(Facet, Debug)]
861 struct Flags {
862 enabled: bool,
863 debug: bool,
864 }
865
866 let value = Flags {
867 enabled: true,
868 debug: false,
869 };
870 let result = to_string(&value).unwrap();
871 assert!(result.contains("enabled true"));
872 assert!(result.contains("debug false"));
873 }
874
875 #[test]
876 fn test_bare_scalar_rules() {
877 use styx_format::can_be_bare;
878
879 assert!(can_be_bare("localhost"));
881 assert!(can_be_bare("8080"));
882 assert!(can_be_bare("hello-world"));
883 assert!(can_be_bare("https://example.com/path"));
884
885 assert!(!can_be_bare("")); assert!(!can_be_bare("hello world")); assert!(!can_be_bare("{braces}")); assert!(!can_be_bare("(parens)")); assert!(!can_be_bare("key=value")); assert!(!can_be_bare("@tag")); assert!(!can_be_bare("//comment")); assert!(!can_be_bare("r#raw")); assert!(!can_be_bare("<<HERE")); }
896
897 #[test]
898 fn test_roundtrip_simple() {
899 use crate::from_str;
900
901 #[derive(Facet, Debug, PartialEq)]
902 struct Config {
903 name: String,
904 port: u16,
905 debug: bool,
906 }
907
908 let original = Config {
909 name: "myapp".into(),
910 port: 8080,
911 debug: true,
912 };
913
914 let serialized = to_string(&original).unwrap();
915 let parsed: Config = from_str(&serialized).unwrap();
916
917 assert_eq!(original.name, parsed.name);
918 assert_eq!(original.port, parsed.port);
919 assert_eq!(original.debug, parsed.debug);
920 }
921
922 #[test]
923 fn test_roundtrip_nested() {
924 use crate::from_str;
925
926 #[derive(Facet, Debug, PartialEq)]
927 struct Inner {
928 x: i32,
929 y: i32,
930 }
931
932 #[derive(Facet, Debug, PartialEq)]
933 struct Outer {
934 name: String,
935 point: Inner,
936 }
937
938 let original = Outer {
939 name: "origin".into(),
940 point: Inner { x: 10, y: 20 },
941 };
942
943 let serialized = to_string(&original).unwrap();
944 let parsed: Outer = from_str(&serialized).unwrap();
945
946 assert_eq!(original.name, parsed.name);
947 assert_eq!(original.point.x, parsed.point.x);
948 assert_eq!(original.point.y, parsed.point.y);
949 }
950
951 #[test]
952 fn test_nested_newtype_variants_serialize_as_chained_tags() {
953 use crate::{from_str_expr, peek_to_string_expr};
954
955 #[derive(Facet, Debug, PartialEq)]
956 #[facet(rename_all = "snake_case")]
957 #[repr(u8)]
958 enum EventPattern {
959 DiscoverStart { executor: String },
960 DiscoverEnd,
961 ExecStart,
962 }
963
964 #[derive(Facet, Debug, PartialEq)]
965 #[facet(rename_all = "snake_case")]
966 #[repr(u8)]
967 enum EventAssertion {
968 MustEmit(EventPattern),
969 MustNotEmit(EventPattern),
970 }
971
972 let original = EventAssertion::MustEmit(EventPattern::DiscoverStart {
973 executor: "default".into(),
974 });
975
976 let serialized = peek_to_string_expr(Peek::new(&original)).unwrap();
977 assert_eq!(serialized, "@must_emit/@discover_start{executor default}");
978
979 let parsed: EventAssertion = from_str_expr(&serialized).unwrap();
980 assert_eq!(parsed, original);
981 }
982
983 #[test]
984 fn test_nested_unit_variants_serialize_as_chained_tags() {
985 use crate::{from_str_expr, peek_to_string_expr};
986
987 #[derive(Facet, Debug, PartialEq)]
988 #[facet(rename_all = "snake_case")]
989 #[repr(u8)]
990 enum EventPattern {
991 DiscoverEnd,
992 ExecStart,
993 }
994
995 #[derive(Facet, Debug, PartialEq)]
996 #[facet(rename_all = "snake_case")]
997 #[repr(u8)]
998 enum EventAssertion {
999 MustEmit(EventPattern),
1000 MustNotEmit(EventPattern),
1001 }
1002
1003 let original = EventAssertion::MustNotEmit(EventPattern::ExecStart);
1004 let serialized = peek_to_string_expr(Peek::new(&original)).unwrap();
1005 assert_eq!(serialized, "@must_not_emit/@exec_start");
1006
1007 let parsed: EventAssertion = from_str_expr(&serialized).unwrap();
1008 assert_eq!(parsed, original);
1009 }
1010
1011 #[test]
1012 fn test_three_nested_newtype_variants_serialize_as_chained_tags() {
1013 use crate::{from_str_expr, peek_to_string_expr};
1014
1015 #[derive(Facet, Debug, PartialEq)]
1016 #[facet(rename_all = "snake_case")]
1017 #[repr(u8)]
1018 enum Leaf {
1019 Done,
1020 }
1021
1022 #[derive(Facet, Debug, PartialEq)]
1023 #[facet(rename_all = "snake_case")]
1024 #[repr(u8)]
1025 enum Middle {
1026 Inner(Leaf),
1027 }
1028
1029 #[derive(Facet, Debug, PartialEq)]
1030 #[facet(rename_all = "snake_case")]
1031 #[repr(u8)]
1032 enum Outer {
1033 Wrapper(Middle),
1034 }
1035
1036 let original = Outer::Wrapper(Middle::Inner(Leaf::Done));
1037 let serialized = peek_to_string_expr(Peek::new(&original)).unwrap();
1038 assert_eq!(serialized, "@wrapper/@inner/@done");
1039
1040 let parsed: Outer = from_str_expr(&serialized).unwrap();
1041 assert_eq!(parsed, original);
1042 }
1043
1044 #[test]
1045 fn test_nested_scalar_newtype_variants_serialize_as_chained_tags() {
1046 use crate::{from_str_expr, peek_to_string_expr};
1047
1048 #[derive(Facet, Debug, PartialEq)]
1049 #[facet(rename_all = "snake_case")]
1050 #[repr(u8)]
1051 enum EventPattern {
1052 Message(String),
1053 }
1054
1055 #[derive(Facet, Debug, PartialEq)]
1056 #[facet(rename_all = "snake_case")]
1057 #[repr(u8)]
1058 enum EventAssertion {
1059 MustEmit(EventPattern),
1060 }
1061
1062 let original = EventAssertion::MustEmit(EventPattern::Message("hello world".into()));
1063 let serialized = peek_to_string_expr(Peek::new(&original)).unwrap();
1064 assert_eq!(serialized, r#"@must_emit/@message"hello world""#);
1065
1066 let parsed: EventAssertion = from_str_expr(&serialized).unwrap();
1067 assert_eq!(parsed, original);
1068 }
1069
1070 #[test]
1071 fn test_roundtrip_with_vec() {
1072 use crate::from_str;
1073
1074 #[derive(Facet, Debug, PartialEq)]
1075 struct Data {
1076 values: Vec<i32>,
1077 }
1078
1079 let original = Data {
1080 values: vec![1, 2, 3, 4, 5],
1081 };
1082
1083 let serialized = to_string(&original).unwrap();
1084 let parsed: Data = from_str(&serialized).unwrap();
1085
1086 assert_eq!(original.values, parsed.values);
1087 }
1088
1089 #[test]
1090 fn test_roundtrip_quoted_string() {
1091 use crate::from_str;
1092
1093 #[derive(Facet, Debug, PartialEq)]
1094 struct Message {
1095 text: String,
1096 }
1097
1098 let original = Message {
1099 text: "hello world with spaces".into(),
1100 };
1101
1102 let serialized = to_string(&original).unwrap();
1103 let parsed: Message = from_str(&serialized).unwrap();
1104
1105 assert_eq!(original.text, parsed.text);
1106 }
1107
1108 #[test]
1109 fn test_peek_to_string_expr_wraps_objects() {
1110 let value = Simple {
1112 name: "test".into(),
1113 value: 42,
1114 };
1115 let peek = Peek::new(&value);
1116 let result = peek_to_string_expr(peek).unwrap();
1117
1118 assert!(
1120 result.starts_with('{'),
1121 "expression should start with brace: {}",
1122 result
1123 );
1124 assert!(
1125 result.ends_with('}'),
1126 "expression should end with brace: {}",
1127 result
1128 );
1129 assert!(result.contains("name test"));
1130 assert!(result.contains("value 42"));
1131 }
1132
1133 #[test]
1134 fn test_peek_to_string_expr_nested() {
1135 let value = Nested {
1137 inner: Simple {
1138 name: "nested".into(),
1139 value: 123,
1140 },
1141 };
1142 let peek = Peek::new(&value);
1143 let result = peek_to_string_expr(peek).unwrap();
1144
1145 assert!(result.starts_with('{'));
1146 assert!(result.contains("inner {"));
1147 }
1148
1149 #[test]
1150 fn test_peek_to_string_expr_scalar() {
1151 let value: i32 = 42;
1153 let peek = Peek::new(&value);
1154 let result = peek_to_string_expr(peek).unwrap();
1155 assert_eq!(result, "42");
1156 }
1157
1158 #[test]
1159 fn test_doc_metadata_field() {
1160 use crate::schema_types::Documented;
1161
1162 #[derive(Facet, Debug)]
1164 struct Config {
1165 name: Documented<String>,
1166 port: Documented<u16>,
1167 }
1168
1169 let config = Config {
1170 name: Documented::with_doc_line("myapp".into(), "The application name"),
1171 port: Documented::with_doc_line(8080, "Port to listen on"),
1172 };
1173
1174 let serialized = to_string(&config).unwrap();
1175
1176 assert!(serialized.contains("/// The application name\nname myapp"));
1178 assert!(serialized.contains("/// Port to listen on\nport 8080"));
1179 }
1180
1181 #[test]
1182 fn test_field_doc_comments_not_emitted_for_regular_values() {
1183 #[derive(Facet, Debug)]
1186 struct Server {
1187 host: String,
1189 port: u16,
1191 }
1192
1193 let server = Server {
1194 host: "localhost".into(),
1195 port: 8080,
1196 };
1197
1198 let serialized = to_string(&server).unwrap();
1199
1200 assert!(!serialized.contains("///"));
1202 assert!(serialized.contains("host localhost"));
1203 assert!(serialized.contains("port 8080"));
1204 }
1205
1206 #[test]
1207 fn test_hashmap_with_documented_keys_serialize() {
1208 use crate::schema_types::Documented;
1209 use std::collections::HashMap;
1210
1211 let mut map: HashMap<Documented<String>, i32> = HashMap::new();
1213 map.insert(
1214 Documented::with_doc_line("port".to_string(), "The port to listen on"),
1215 8080,
1216 );
1217 map.insert(
1218 Documented::with_doc_line("timeout".to_string(), "Timeout in seconds"),
1219 30,
1220 );
1221
1222 let serialized = to_string(&map).unwrap();
1223 tracing::debug!("Serialized HashMap:\n{}", serialized);
1224
1225 assert!(serialized.contains("/// The port to listen on\nport 8080"));
1227 assert!(serialized.contains("/// Timeout in seconds\ntimeout 30"));
1228 }
1229
1230 #[test]
1231 fn test_hashmap_with_documented_keys_roundtrip() {
1232 use crate::schema_types::Documented;
1233 use std::collections::HashMap;
1234
1235 let input = r#"
1237/// The port to listen on
1238port 8080
1239/// Timeout in seconds
1240timeout 30
1241"#;
1242
1243 let parsed: HashMap<Documented<String>, i32> =
1244 crate::from_str(input).expect("should parse");
1245
1246 tracing::debug!("Parsed HashMap: {:?}", parsed);
1247
1248 assert_eq!(
1250 parsed.get(&Documented::new("port".to_string())),
1251 Some(&8080)
1252 );
1253 assert_eq!(
1254 parsed.get(&Documented::new("timeout".to_string())),
1255 Some(&30)
1256 );
1257
1258 let port_key = parsed
1260 .keys()
1261 .find(|k| k.value == "port")
1262 .expect("should have port key");
1263 assert_eq!(
1264 port_key.doc(),
1265 Some(&["The port to listen on".to_string()][..])
1266 );
1267
1268 let timeout_key = parsed
1269 .keys()
1270 .find(|k| k.value == "timeout")
1271 .expect("should have timeout key");
1272 assert_eq!(
1273 timeout_key.doc(),
1274 Some(&["Timeout in seconds".to_string()][..])
1275 );
1276 }
1277}