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(FieldKey {
63 name: name_value,
64 tag: tag_value,
65 doc: if doc_lines.is_empty() {
66 None
67 } else {
68 Some(doc_lines)
69 },
70 location: FieldLocationHint::KeyValue,
71 });
72 }
73
74 if let Ok(opt) = key.into_option() {
76 return match opt.value() {
77 Some(inner) => inner
78 .as_str()
79 .map(|s| FieldKey::new(s, FieldLocationHint::KeyValue)),
80 None => {
81 Some(FieldKey::unit(FieldLocationHint::KeyValue))
83 }
84 };
85 }
86
87 if let Some(s) = key.as_str() {
89 return Some(FieldKey::new(s, FieldLocationHint::KeyValue));
90 }
91
92 None
93}
94
95fn extract_name_and_tag<'mem, 'facet>(
97 value: Peek<'mem, 'facet>,
98) -> (Option<Cow<'mem, str>>, Option<Cow<'mem, str>>) {
99 if let Some(s) = value.as_str() {
101 return (Some(Cow::Borrowed(s)), None);
102 }
103
104 if let Ok(opt) = value.into_option() {
106 return match opt.value() {
107 Some(inner) => {
108 if let Some(s) = inner.as_str() {
109 (Some(Cow::Borrowed(s)), None)
110 } else {
111 (None, None)
112 }
113 }
114 None => (None, None),
115 };
116 }
117
118 if value.shape().is_metadata_container()
120 && let Ok(container) = value.into_struct()
121 {
122 let mut name: Option<Cow<'mem, str>> = None;
123 let mut tag: Option<Cow<'mem, str>> = None;
124
125 for (f, field_value) in container.fields() {
126 if f.metadata_kind() == Some("tag") {
127 if let Ok(opt) = field_value.into_option()
128 && let Some(inner) = opt.value()
129 && let Some(s) = inner.as_str()
130 {
131 tag = Some(Cow::Borrowed(s));
132 }
133 } else if f.metadata_kind().is_none() {
134 if let Some(s) = field_value.as_str() {
136 name = Some(Cow::Borrowed(s));
137 } else if let Ok(opt) = field_value.into_option()
138 && let Some(inner) = opt.value()
139 && let Some(s) = inner.as_str()
140 {
141 name = Some(Cow::Borrowed(s));
142 }
143 }
144 }
145
146 return (name, tag);
147 }
148
149 (None, None)
150}
151
152#[derive(Debug)]
154pub struct StyxSerializeError {
155 msg: Cow<'static, str>,
156}
157
158impl StyxSerializeError {
159 fn new(msg: impl Into<Cow<'static, str>>) -> Self {
160 Self { msg: msg.into() }
161 }
162}
163
164impl core::fmt::Display for StyxSerializeError {
165 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166 f.write_str(&self.msg)
167 }
168}
169
170impl std::error::Error for StyxSerializeError {}
171
172pub struct StyxSerializer {
174 writer: StyxWriter,
175 at_root: bool,
177 just_wrote_tag: bool,
179}
180
181impl StyxSerializer {
182 pub fn new() -> Self {
184 Self::with_options(FormatOptions::default())
185 }
186
187 pub fn with_options(options: FormatOptions) -> Self {
189 Self {
190 writer: StyxWriter::with_options(options),
191 at_root: true,
192 just_wrote_tag: false,
193 }
194 }
195
196 pub fn finish(self) -> Vec<u8> {
198 self.writer.finish_document()
199 }
200}
201
202impl Default for StyxSerializer {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208impl FormatSerializer for StyxSerializer {
209 type Error = StyxSerializeError;
210
211 fn begin_struct(&mut self) -> Result<(), Self::Error> {
212 let is_root = self.at_root;
213 trace!(is_root, "begin_struct");
214 self.at_root = false;
215 self.writer.begin_struct(is_root);
216 Ok(())
217 }
218
219 fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
220 trace!(key, "field_key");
221 self.writer.field_key(key).map_err(StyxSerializeError::new)
222 }
223
224 fn emit_field_key(&mut self, key: &facet_format::FieldKey<'_>) -> Result<(), Self::Error> {
225 trace!(?key, "emit_field_key");
226
227 let doc_lines: Vec<&str> = key
228 .doc
229 .as_ref()
230 .map(|d| d.iter().map(|s| s.as_ref()).collect())
231 .unwrap_or_default();
232
233 match (key.tag.as_deref(), key.name.as_deref()) {
239 (Some(tag), Some(name)) => {
240 let key_str = if tag.is_empty() {
242 format!("@\"{}\"", name)
243 } else {
244 format!("@{}\"{}\"", tag, name)
245 };
246 if !doc_lines.is_empty() {
247 self.writer
248 .write_doc_comment_and_key_raw(&doc_lines.join("\n"), &key_str);
249 } else {
250 self.writer
251 .field_key_raw(&key_str)
252 .map_err(StyxSerializeError::new)?;
253 }
254 }
255 (Some(tag), None) => {
256 let key_str = if tag.is_empty() {
258 "@".to_string()
259 } else {
260 format!("@{}", tag)
261 };
262 if !doc_lines.is_empty() {
263 self.writer
264 .write_doc_comment_and_key_raw(&doc_lines.join("\n"), &key_str);
265 } else {
266 self.writer
267 .field_key_raw(&key_str)
268 .map_err(StyxSerializeError::new)?;
269 }
270 }
271 (None, Some(name)) => {
272 if !doc_lines.is_empty() {
274 self.writer
275 .write_doc_comment_and_key(&doc_lines.join("\n"), name);
276 } else {
277 self.writer
278 .field_key(name)
279 .map_err(StyxSerializeError::new)?;
280 }
281 }
282 (None, None) => {
283 if !doc_lines.is_empty() {
285 self.writer
286 .write_doc_comment_and_key_raw(&doc_lines.join("\n"), "@");
287 } else {
288 self.writer
289 .field_key_raw("@")
290 .map_err(StyxSerializeError::new)?;
291 }
292 }
293 }
294 Ok(())
295 }
296
297 fn end_struct(&mut self) -> Result<(), Self::Error> {
298 trace!("end_struct");
299 self.writer.end_struct().map_err(StyxSerializeError::new)
300 }
301
302 fn begin_seq(&mut self) -> Result<(), Self::Error> {
303 trace!("begin_seq");
304 self.at_root = false;
305 self.writer.begin_seq();
306 Ok(())
307 }
308
309 fn end_seq(&mut self) -> Result<(), Self::Error> {
310 trace!("end_seq");
311 self.writer.end_seq().map_err(StyxSerializeError::new)
312 }
313
314 fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
315 trace!(?scalar, "scalar");
316 self.at_root = false;
317 self.just_wrote_tag = false;
318 match scalar {
319 ScalarValue::Unit | ScalarValue::Null => self.writer.write_null(),
320 ScalarValue::Bool(v) => self.writer.write_bool(v),
321 ScalarValue::Char(c) => self.writer.write_char(c),
322 ScalarValue::I64(v) => self.writer.write_i64(v),
323 ScalarValue::U64(v) => self.writer.write_u64(v),
324 ScalarValue::I128(v) => self.writer.write_i128(v),
325 ScalarValue::U128(v) => self.writer.write_u128(v),
326 ScalarValue::F64(v) => self.writer.write_f64(v),
327 ScalarValue::Str(s) => self.writer.write_string(&s),
328 ScalarValue::Bytes(bytes) => self.writer.write_bytes(&bytes),
329 }
330 Ok(())
331 }
332
333 fn serialize_none(&mut self) -> Result<(), Self::Error> {
334 trace!(just_wrote_tag = self.just_wrote_tag, "serialize_none");
335 if self.just_wrote_tag {
337 self.just_wrote_tag = false;
338 self.writer.clear_skip_before_value();
340 return Ok(());
341 }
342 self.at_root = false;
343 self.writer.write_null();
344 Ok(())
345 }
346
347 fn write_variant_tag(&mut self, variant_name: &str) -> Result<bool, Self::Error> {
348 trace!(variant_name, "write_variant_tag");
349 self.at_root = false;
350 self.just_wrote_tag = true;
351 self.writer.write_tag(variant_name);
352 Ok(true)
353 }
354
355 fn begin_struct_after_tag(&mut self) -> Result<(), Self::Error> {
356 trace!("begin_struct_after_tag");
357 self.just_wrote_tag = false;
358 self.writer.begin_struct_after_tag(false);
359 Ok(())
360 }
361
362 fn begin_seq_after_tag(&mut self) -> Result<(), Self::Error> {
363 trace!("begin_seq_after_tag");
364 self.just_wrote_tag = false;
365 self.writer.begin_seq_after_tag();
366 Ok(())
367 }
368
369 fn raw_serialize_shape(&self) -> Option<&'static facet_core::Shape> {
370 Some(crate::RawStyx::SHAPE)
371 }
372
373 fn raw_scalar(&mut self, content: &str) -> Result<(), Self::Error> {
374 trace!(content, "raw_scalar");
375 self.at_root = false;
377 self.just_wrote_tag = false;
378 self.writer.before_value();
379 self.writer.write_str(content);
380 Ok(())
381 }
382
383 fn serialize_map_key(&mut self, key: Peek<'_, '_>) -> Result<bool, Self::Error> {
384 trace!(shape = key.shape().type_identifier, "serialize_map_key");
385
386 if let Some(field_key) = extract_field_key(key) {
388 trace!(?field_key, "serialize_map_key: extracted FieldKey");
389 self.emit_field_key(&field_key)?;
390 return Ok(true);
391 }
392
393 trace!("serialize_map_key: falling back to default");
395 Ok(false)
396 }
397
398 fn field_metadata_with_value(
399 &mut self,
400 field_item: &facet_reflect::FieldItem,
401 value: Peek<'_, '_>,
402 ) -> Result<bool, Self::Error> {
403 let is_metadata_container = value.shape().is_metadata_container();
404 trace!(
405 field_name = field_item.effective_name(),
406 is_metadata_container,
407 value_shape = value.shape().type_identifier,
408 "field_metadata_with_value"
409 );
410
411 if is_metadata_container && let Ok(container) = value.into_struct() {
414 let mut doc_lines: Vec<&str> = Vec::new();
416 for (f, field_value) in container.fields() {
417 trace!(
418 metadata_kind = ?f.metadata_kind(),
419 field = f.effective_name(),
420 "field_metadata_with_value: inspecting container field"
421 );
422 if f.metadata_kind() == Some("doc")
423 && let Ok(opt) = field_value.into_option()
424 && let Some(inner) = opt.value()
425 && let Ok(list) = inner.into_list_like()
426 {
427 for item in list.iter() {
428 if let Some(line) = item.as_str() {
429 doc_lines.push(line);
430 }
431 }
432 }
433 }
434
435 if !doc_lines.is_empty() {
437 trace!(doc_lines = ?doc_lines, "field_metadata_with_value: emitting doc comment");
438 let doc = doc_lines.join("\n");
439 self.writer
440 .write_doc_comment_and_key(&doc, field_item.effective_name());
441 return Ok(true);
442 }
443 }
444
445 Ok(false)
451 }
452}
453
454pub fn to_string<'facet, T>(value: &T) -> Result<String, SerializeError<StyxSerializeError>>
478where
479 T: Facet<'facet> + ?Sized,
480{
481 to_string_with_options(value, &FormatOptions::default())
482}
483
484pub fn to_string_compact<'facet, T>(value: &T) -> Result<String, SerializeError<StyxSerializeError>>
500where
501 T: Facet<'facet> + ?Sized,
502{
503 let options = FormatOptions::default().inline();
505 let mut serializer = CompactStyxSerializer::with_options(options);
506 serialize_root(&mut serializer, Peek::new(value))?;
507 let bytes = serializer.finish();
508 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
509}
510
511pub fn to_string_with_options<'facet, T>(
513 value: &T,
514 options: &FormatOptions,
515) -> Result<String, SerializeError<StyxSerializeError>>
516where
517 T: Facet<'facet> + ?Sized,
518{
519 let mut serializer = StyxSerializer::with_options(options.clone());
520 serialize_root(&mut serializer, Peek::new(value))?;
521 let bytes = serializer.finish();
522 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
523}
524
525pub fn peek_to_string<'input, 'facet>(
527 peek: Peek<'input, 'facet>,
528) -> Result<String, SerializeError<StyxSerializeError>> {
529 peek_to_string_with_options(peek, &FormatOptions::default())
530}
531
532pub fn peek_to_string_with_options<'input, 'facet>(
534 peek: Peek<'input, 'facet>,
535 options: &FormatOptions,
536) -> Result<String, SerializeError<StyxSerializeError>> {
537 let mut serializer = StyxSerializer::with_options(options.clone());
538 serialize_root(&mut serializer, peek)?;
539 let bytes = serializer.finish();
540 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
541}
542
543pub fn peek_to_string_expr<'input, 'facet>(
548 peek: Peek<'input, 'facet>,
549) -> Result<String, SerializeError<StyxSerializeError>> {
550 let options = FormatOptions::default().inline();
551 let mut serializer = CompactStyxSerializer::with_options(options);
552 serialize_root(&mut serializer, peek)?;
553 let bytes = serializer.finish();
554 Ok(String::from_utf8(bytes).expect("Styx output should always be valid UTF-8"))
555}
556
557struct CompactStyxSerializer {
563 writer: StyxWriter,
564}
565
566impl CompactStyxSerializer {
567 fn with_options(options: FormatOptions) -> Self {
568 Self {
569 writer: StyxWriter::with_options(options),
570 }
571 }
572
573 fn finish(self) -> Vec<u8> {
574 self.writer.finish()
576 }
577}
578
579impl FormatSerializer for CompactStyxSerializer {
580 type Error = StyxSerializeError;
581
582 fn begin_struct(&mut self) -> Result<(), Self::Error> {
583 self.writer.begin_struct(false);
585 Ok(())
586 }
587
588 fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
589 self.writer.field_key(key).map_err(StyxSerializeError::new)
590 }
591
592 fn end_struct(&mut self) -> Result<(), Self::Error> {
593 self.writer.end_struct().map_err(StyxSerializeError::new)
594 }
595
596 fn begin_seq(&mut self) -> Result<(), Self::Error> {
597 self.writer.begin_seq();
598 Ok(())
599 }
600
601 fn end_seq(&mut self) -> Result<(), Self::Error> {
602 self.writer.end_seq().map_err(StyxSerializeError::new)
603 }
604
605 fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
606 match scalar {
607 ScalarValue::Unit | ScalarValue::Null => self.writer.write_null(),
608 ScalarValue::Bool(v) => self.writer.write_bool(v),
609 ScalarValue::Char(c) => self.writer.write_char(c),
610 ScalarValue::I64(v) => self.writer.write_i64(v),
611 ScalarValue::U64(v) => self.writer.write_u64(v),
612 ScalarValue::I128(v) => self.writer.write_i128(v),
613 ScalarValue::U128(v) => self.writer.write_u128(v),
614 ScalarValue::F64(v) => self.writer.write_f64(v),
615 ScalarValue::Str(s) => self.writer.write_string(&s),
616 ScalarValue::Bytes(bytes) => self.writer.write_bytes(&bytes),
617 }
618 Ok(())
619 }
620
621 fn serialize_none(&mut self) -> Result<(), Self::Error> {
622 self.writer.write_null();
623 Ok(())
624 }
625
626 fn write_variant_tag(&mut self, variant_name: &str) -> Result<bool, Self::Error> {
627 self.writer.write_tag(variant_name);
628 Ok(true)
629 }
630
631 fn begin_struct_after_tag(&mut self) -> Result<(), Self::Error> {
632 self.writer.begin_struct_after_tag(false);
633 Ok(())
634 }
635
636 fn begin_seq_after_tag(&mut self) -> Result<(), Self::Error> {
637 self.writer.begin_seq_after_tag();
638 Ok(())
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645 use facet::Facet;
646 use facet_testhelpers::test;
647
648 #[derive(Facet, Debug)]
649 struct Simple {
650 name: String,
651 value: i32,
652 }
653
654 #[derive(Facet, Debug)]
655 struct Nested {
656 inner: Simple,
657 }
658
659 #[derive(Facet, Debug)]
660 struct WithVec {
661 items: Vec<i32>,
662 }
663
664 #[derive(Facet, Debug)]
665 struct WithOptional {
666 required: String,
667 optional: Option<i32>,
668 }
669
670 #[test]
671 fn test_simple_struct() {
672 let value = Simple {
673 name: "hello".into(),
674 value: 42,
675 };
676 let result = to_string(&value).unwrap();
677 assert!(result.contains("name hello"));
678 assert!(result.contains("value 42"));
679 }
680
681 #[test]
682 fn test_compact_struct() {
683 let value = Simple {
684 name: "hello".into(),
685 value: 42,
686 };
687 let result = to_string_compact(&value).unwrap();
688 assert_eq!(result, "{name hello, value 42}");
689 }
690
691 #[test]
692 fn test_nested_struct() {
693 let value = Nested {
694 inner: Simple {
695 name: "test".into(),
696 value: 123,
697 },
698 };
699 let result = to_string(&value).unwrap();
700 assert!(result.contains("inner"));
701 assert!(result.contains("{name test, value 123}"));
703 }
704
705 #[test]
706 fn test_sequence() {
707 let value = WithVec {
708 items: vec![1, 2, 3, 4, 5],
709 };
710 let result = to_string(&value).unwrap();
711 assert!(result.contains("items (1 2 3 4 5)"));
712 }
713
714 #[test]
715 fn test_quoted_string() {
716 let value = Simple {
717 name: "hello world".into(), value: 42,
719 };
720 let result = to_string(&value).unwrap();
721 assert!(result.contains("name \"hello world\""));
722 }
723
724 #[test]
725 fn test_special_chars_need_quoting() {
726 let value = Simple {
727 name: "{braces}".into(),
728 value: 42,
729 };
730 let result = to_string(&value).unwrap();
731 assert!(result.contains("name \"{braces}\""));
732 }
733
734 #[test]
735 fn test_optional_none() {
736 let value = WithOptional {
737 required: "hello".into(),
738 optional: None,
739 };
740 let result = to_string(&value).unwrap();
741 assert!(result.contains("required hello"));
742 assert!(result.contains("optional @"));
744 }
745
746 #[test]
747 fn test_optional_some() {
748 let value = WithOptional {
749 required: "hello".into(),
750 optional: Some(42),
751 };
752 let result = to_string(&value).unwrap();
753 assert!(result.contains("required hello"));
754 assert!(result.contains("optional 42"));
755 }
756
757 #[test]
758 fn test_bool_values() {
759 #[derive(Facet, Debug)]
760 struct Flags {
761 enabled: bool,
762 debug: bool,
763 }
764
765 let value = Flags {
766 enabled: true,
767 debug: false,
768 };
769 let result = to_string(&value).unwrap();
770 assert!(result.contains("enabled true"));
771 assert!(result.contains("debug false"));
772 }
773
774 #[test]
775 fn test_bare_scalar_rules() {
776 use styx_format::can_be_bare;
777
778 assert!(can_be_bare("localhost"));
780 assert!(can_be_bare("8080"));
781 assert!(can_be_bare("hello-world"));
782 assert!(can_be_bare("https://example.com/path"));
783
784 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")); }
795
796 #[test]
797 fn test_roundtrip_simple() {
798 use crate::from_str;
799
800 #[derive(Facet, Debug, PartialEq)]
801 struct Config {
802 name: String,
803 port: u16,
804 debug: bool,
805 }
806
807 let original = Config {
808 name: "myapp".into(),
809 port: 8080,
810 debug: true,
811 };
812
813 let serialized = to_string(&original).unwrap();
814 let parsed: Config = from_str(&serialized).unwrap();
815
816 assert_eq!(original.name, parsed.name);
817 assert_eq!(original.port, parsed.port);
818 assert_eq!(original.debug, parsed.debug);
819 }
820
821 #[test]
822 fn test_roundtrip_nested() {
823 use crate::from_str;
824
825 #[derive(Facet, Debug, PartialEq)]
826 struct Inner {
827 x: i32,
828 y: i32,
829 }
830
831 #[derive(Facet, Debug, PartialEq)]
832 struct Outer {
833 name: String,
834 point: Inner,
835 }
836
837 let original = Outer {
838 name: "origin".into(),
839 point: Inner { x: 10, y: 20 },
840 };
841
842 let serialized = to_string(&original).unwrap();
843 let parsed: Outer = from_str(&serialized).unwrap();
844
845 assert_eq!(original.name, parsed.name);
846 assert_eq!(original.point.x, parsed.point.x);
847 assert_eq!(original.point.y, parsed.point.y);
848 }
849
850 #[test]
851 fn test_roundtrip_with_vec() {
852 use crate::from_str;
853
854 #[derive(Facet, Debug, PartialEq)]
855 struct Data {
856 values: Vec<i32>,
857 }
858
859 let original = Data {
860 values: vec![1, 2, 3, 4, 5],
861 };
862
863 let serialized = to_string(&original).unwrap();
864 let parsed: Data = from_str(&serialized).unwrap();
865
866 assert_eq!(original.values, parsed.values);
867 }
868
869 #[test]
870 fn test_roundtrip_quoted_string() {
871 use crate::from_str;
872
873 #[derive(Facet, Debug, PartialEq)]
874 struct Message {
875 text: String,
876 }
877
878 let original = Message {
879 text: "hello world with spaces".into(),
880 };
881
882 let serialized = to_string(&original).unwrap();
883 let parsed: Message = from_str(&serialized).unwrap();
884
885 assert_eq!(original.text, parsed.text);
886 }
887
888 #[test]
889 fn test_peek_to_string_expr_wraps_objects() {
890 let value = Simple {
892 name: "test".into(),
893 value: 42,
894 };
895 let peek = Peek::new(&value);
896 let result = peek_to_string_expr(peek).unwrap();
897
898 assert!(
900 result.starts_with('{'),
901 "expression should start with brace: {}",
902 result
903 );
904 assert!(
905 result.ends_with('}'),
906 "expression should end with brace: {}",
907 result
908 );
909 assert!(result.contains("name test"));
910 assert!(result.contains("value 42"));
911 }
912
913 #[test]
914 fn test_peek_to_string_expr_nested() {
915 let value = Nested {
917 inner: Simple {
918 name: "nested".into(),
919 value: 123,
920 },
921 };
922 let peek = Peek::new(&value);
923 let result = peek_to_string_expr(peek).unwrap();
924
925 assert!(result.starts_with('{'));
926 assert!(result.contains("inner {"));
927 }
928
929 #[test]
930 fn test_peek_to_string_expr_scalar() {
931 let value: i32 = 42;
933 let peek = Peek::new(&value);
934 let result = peek_to_string_expr(peek).unwrap();
935 assert_eq!(result, "42");
936 }
937
938 #[test]
939 fn test_doc_metadata_field() {
940 use crate::schema_types::Documented;
941
942 #[derive(Facet, Debug)]
944 struct Config {
945 name: Documented<String>,
946 port: Documented<u16>,
947 }
948
949 let config = Config {
950 name: Documented::with_doc_line("myapp".into(), "The application name"),
951 port: Documented::with_doc_line(8080, "Port to listen on"),
952 };
953
954 let serialized = to_string(&config).unwrap();
955
956 assert!(serialized.contains("/// The application name\nname myapp"));
958 assert!(serialized.contains("/// Port to listen on\nport 8080"));
959 }
960
961 #[test]
962 fn test_field_doc_comments_not_emitted_for_regular_values() {
963 #[derive(Facet, Debug)]
966 struct Server {
967 host: String,
969 port: u16,
971 }
972
973 let server = Server {
974 host: "localhost".into(),
975 port: 8080,
976 };
977
978 let serialized = to_string(&server).unwrap();
979
980 assert!(!serialized.contains("///"));
982 assert!(serialized.contains("host localhost"));
983 assert!(serialized.contains("port 8080"));
984 }
985
986 #[test]
987 fn test_hashmap_with_documented_keys_serialize() {
988 use crate::schema_types::Documented;
989 use std::collections::HashMap;
990
991 let mut map: HashMap<Documented<String>, i32> = HashMap::new();
993 map.insert(
994 Documented::with_doc_line("port".to_string(), "The port to listen on"),
995 8080,
996 );
997 map.insert(
998 Documented::with_doc_line("timeout".to_string(), "Timeout in seconds"),
999 30,
1000 );
1001
1002 let serialized = to_string(&map).unwrap();
1003 tracing::debug!("Serialized HashMap:\n{}", serialized);
1004
1005 assert!(serialized.contains("/// The port to listen on\nport 8080"));
1007 assert!(serialized.contains("/// Timeout in seconds\ntimeout 30"));
1008 }
1009
1010 #[test]
1011 fn test_hashmap_with_documented_keys_roundtrip() {
1012 use crate::schema_types::Documented;
1013 use std::collections::HashMap;
1014
1015 let input = r#"
1017/// The port to listen on
1018port 8080
1019/// Timeout in seconds
1020timeout 30
1021"#;
1022
1023 let parsed: HashMap<Documented<String>, i32> =
1024 crate::from_str(input).expect("should parse");
1025
1026 tracing::debug!("Parsed HashMap: {:?}", parsed);
1027
1028 assert_eq!(
1030 parsed.get(&Documented::new("port".to_string())),
1031 Some(&8080)
1032 );
1033 assert_eq!(
1034 parsed.get(&Documented::new("timeout".to_string())),
1035 Some(&30)
1036 );
1037
1038 let port_key = parsed
1040 .keys()
1041 .find(|k| k.value == "port")
1042 .expect("should have port key");
1043 assert_eq!(
1044 port_key.doc(),
1045 Some(&["The port to listen on".to_string()][..])
1046 );
1047
1048 let timeout_key = parsed
1049 .keys()
1050 .find(|k| k.value == "timeout")
1051 .expect("should have timeout key");
1052 assert_eq!(
1053 timeout_key.doc(),
1054 Some(&["Timeout in seconds".to_string()][..])
1055 );
1056 }
1057}