1use alloc::borrow::Cow;
7use alloc::collections::BTreeMap;
8use alloc::collections::BTreeSet;
9use alloc::string::String;
10use alloc::vec;
11use alloc::vec::Vec;
12use core::fmt::Write;
13
14use facet_core::{
15 Attr, Def, EnumRepr, EnumType, Field, PointerType, Shape, StructKind, StructType, Type,
16 UserType, Variant,
17};
18use owo_colors::OwoColorize;
19
20pub mod colors {
22 use owo_colors::Style;
23
24 pub fn keyword() -> Style {
26 Style::new().fg_rgb::<187, 154, 247>()
27 }
28
29 pub fn type_name() -> Style {
31 Style::new().fg_rgb::<192, 202, 245>()
32 }
33
34 pub fn field_name() -> Style {
36 Style::new().fg_rgb::<125, 207, 255>()
37 }
38
39 pub fn primitive() -> Style {
41 Style::new().fg_rgb::<115, 218, 202>()
42 }
43
44 pub fn punctuation() -> Style {
46 Style::new().fg_rgb::<154, 165, 206>()
47 }
48
49 pub fn attribute() -> Style {
51 Style::new().fg_rgb::<137, 221, 255>()
52 }
53
54 pub fn attribute_content() -> Style {
56 Style::new().fg_rgb::<122, 162, 247>()
57 }
58
59 pub fn string() -> Style {
61 Style::new().fg_rgb::<158, 206, 106>()
62 }
63
64 pub fn container() -> Style {
66 Style::new().fg_rgb::<255, 158, 100>()
67 }
68
69 pub fn comment() -> Style {
71 Style::new().fg_rgb::<86, 95, 137>()
72 }
73}
74
75#[derive(Clone, Debug, Default)]
77pub struct ShapeFormatConfig {
78 pub show_doc_comments: bool,
80 pub show_third_party_attrs: bool,
82}
83
84impl ShapeFormatConfig {
85 pub fn new() -> Self {
87 Self::default()
88 }
89
90 pub fn with_doc_comments(mut self) -> Self {
92 self.show_doc_comments = true;
93 self
94 }
95
96 pub fn with_third_party_attrs(mut self) -> Self {
98 self.show_third_party_attrs = true;
99 self
100 }
101
102 pub fn with_all_metadata(mut self) -> Self {
104 self.show_doc_comments = true;
105 self.show_third_party_attrs = true;
106 self
107 }
108}
109
110#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
112pub enum PathSegment {
113 Field(Cow<'static, str>),
115 Variant(Cow<'static, str>),
117 Index(usize),
119 Key(Cow<'static, str>),
121}
122
123pub type Path = Vec<PathSegment>;
125
126pub type Span = (usize, usize);
128
129#[derive(Clone, Debug, Default, PartialEq, Eq)]
131pub struct FieldSpan {
132 pub key: Span,
134 pub value: Span,
136}
137
138#[derive(Debug)]
140pub struct FormattedShape {
141 pub text: String,
143 pub spans: BTreeMap<Path, FieldSpan>,
145}
146
147pub fn strip_ansi(s: &str) -> String {
149 let mut result = String::with_capacity(s.len());
150 let mut chars = s.chars().peekable();
151
152 while let Some(c) = chars.next() {
153 if c == '\x1b' {
154 if chars.peek() == Some(&'[') {
156 chars.next(); while let Some(&next) = chars.peek() {
159 chars.next();
160 if next.is_ascii_alphabetic() {
161 break;
162 }
163 }
164 }
165 } else {
166 result.push(c);
167 }
168 }
169 result
170}
171
172pub fn format_shape(shape: &Shape) -> String {
176 strip_ansi(&format_shape_colored(shape))
177}
178
179pub fn format_shape_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
181 strip_ansi(&format_shape_colored_with_config(shape, config))
182}
183
184pub fn format_shape_with_spans(shape: &Shape) -> FormattedShape {
187 let mut ctx = SpanTrackingContext::new();
188 format_shape_into_with_spans(shape, &mut ctx).expect("Formatting failed");
189 FormattedShape {
190 text: ctx.output,
191 spans: ctx.spans,
192 }
193}
194
195pub fn format_shape_colored(shape: &Shape) -> String {
199 format_shape_colored_with_config(shape, &ShapeFormatConfig::default().with_all_metadata())
200}
201
202pub fn format_shape_colored_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
204 let mut output = String::new();
205 format_shape_colored_into_with_config(shape, &mut output, config).expect("Formatting failed");
206 output
207}
208
209pub fn format_shape_colored_into(shape: &Shape, output: &mut String) -> core::fmt::Result {
211 format_shape_colored_into_with_config(shape, output, &ShapeFormatConfig::default())
212}
213
214pub fn format_shape_colored_into_with_config(
216 shape: &Shape,
217 output: &mut String,
218 config: &ShapeFormatConfig,
219) -> core::fmt::Result {
220 let mut printed: BTreeSet<&'static str> = BTreeSet::new();
221 let mut queue: Vec<&Shape> = Vec::new();
222 queue.push(shape);
223
224 while let Some(current) = queue.pop() {
225 if !printed.insert(current.type_identifier) {
226 continue;
227 }
228
229 if printed.len() > 1 {
230 writeln!(output)?;
231 writeln!(output)?;
232 }
233
234 match current.def {
235 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
236 printed.remove(current.type_identifier);
237 continue;
238 }
239 _ => {}
240 }
241
242 match ¤t.ty {
243 Type::User(user_type) => match user_type {
244 UserType::Struct(struct_type) => {
245 format_struct_colored(current, struct_type, output, config)?;
246 collect_nested_types(struct_type, &mut queue);
247 }
248 UserType::Enum(enum_type) => {
249 format_enum_colored(current, enum_type, output, config)?;
250 for variant in enum_type.variants {
251 collect_nested_types(&variant.data, &mut queue);
252 }
253 }
254 UserType::Union(_) | UserType::Opaque => {
255 printed.remove(current.type_identifier);
256 }
257 },
258 _ => {
259 printed.remove(current.type_identifier);
260 }
261 }
262 }
263 Ok(())
264}
265
266fn format_struct_colored(
267 shape: &Shape,
268 struct_type: &StructType,
269 output: &mut String,
270 config: &ShapeFormatConfig,
271) -> core::fmt::Result {
272 if config.show_doc_comments {
274 write_doc_comments_colored(shape.doc, output, "")?;
275 }
276
277 write!(output, "{}", "#[".style(colors::attribute()))?;
279 write!(output, "{}", "derive".style(colors::attribute_content()))?;
280 write!(output, "{}", "(".style(colors::attribute()))?;
281 write!(output, "{}", "Facet".style(colors::attribute_content()))?;
282 writeln!(output, "{}", ")]".style(colors::attribute()))?;
283
284 write_facet_attrs_colored(shape, output)?;
286
287 if config.show_third_party_attrs {
289 write_third_party_attrs_colored(shape.attributes, output, "")?;
290 }
291
292 match struct_type.kind {
293 StructKind::Struct => {
294 write!(output, "{} ", "struct".style(colors::keyword()))?;
295 write!(
296 output,
297 "{}",
298 shape.type_identifier.style(colors::type_name())
299 )?;
300 writeln!(output, " {}", "{".style(colors::punctuation()))?;
301
302 for (i, field) in struct_type.fields.iter().enumerate() {
303 if i > 0 {
305 writeln!(output)?;
306 }
307 if config.show_doc_comments {
309 write_doc_comments_colored(field.doc, output, " ")?;
310 }
311 if config.show_third_party_attrs {
313 write_field_third_party_attrs_colored(field, output, " ")?;
314 }
315 write!(output, " {}", field.name.style(colors::field_name()))?;
316 write!(output, "{} ", ":".style(colors::punctuation()))?;
317 write_type_name_colored(field.shape(), output)?;
318 writeln!(output, "{}", ",".style(colors::punctuation()))?;
319 }
320 write!(output, "{}", "}".style(colors::punctuation()))?;
321 }
322 StructKind::Tuple | StructKind::TupleStruct => {
323 write!(output, "{} ", "struct".style(colors::keyword()))?;
324 write!(
325 output,
326 "{}",
327 shape.type_identifier.style(colors::type_name())
328 )?;
329 write!(output, "{}", "(".style(colors::punctuation()))?;
330 for (i, field) in struct_type.fields.iter().enumerate() {
331 if i > 0 {
332 write!(output, "{} ", ",".style(colors::punctuation()))?;
333 }
334 write_type_name_colored(field.shape(), output)?;
335 }
336 write!(
337 output,
338 "{}{}",
339 ")".style(colors::punctuation()),
340 ";".style(colors::punctuation())
341 )?;
342 }
343 StructKind::Unit => {
344 write!(output, "{} ", "struct".style(colors::keyword()))?;
345 write!(
346 output,
347 "{}",
348 shape.type_identifier.style(colors::type_name())
349 )?;
350 write!(output, "{}", ";".style(colors::punctuation()))?;
351 }
352 }
353 Ok(())
354}
355
356fn format_enum_colored(
357 shape: &Shape,
358 enum_type: &EnumType,
359 output: &mut String,
360 config: &ShapeFormatConfig,
361) -> core::fmt::Result {
362 if config.show_doc_comments {
364 write_doc_comments_colored(shape.doc, output, "")?;
365 }
366
367 write!(output, "{}", "#[".style(colors::attribute()))?;
369 write!(output, "{}", "derive".style(colors::attribute_content()))?;
370 write!(output, "{}", "(".style(colors::attribute()))?;
371 write!(output, "{}", "Facet".style(colors::attribute_content()))?;
372 writeln!(output, "{}", ")]".style(colors::attribute()))?;
373
374 let repr_str = match enum_type.enum_repr {
376 EnumRepr::RustNPO => None,
377 EnumRepr::U8 => Some("u8"),
378 EnumRepr::U16 => Some("u16"),
379 EnumRepr::U32 => Some("u32"),
380 EnumRepr::U64 => Some("u64"),
381 EnumRepr::USize => Some("usize"),
382 EnumRepr::I8 => Some("i8"),
383 EnumRepr::I16 => Some("i16"),
384 EnumRepr::I32 => Some("i32"),
385 EnumRepr::I64 => Some("i64"),
386 EnumRepr::ISize => Some("isize"),
387 };
388
389 if let Some(repr) = repr_str {
390 write!(output, "{}", "#[".style(colors::attribute()))?;
391 write!(output, "{}", "repr".style(colors::attribute_content()))?;
392 write!(output, "{}", "(".style(colors::attribute()))?;
393 write!(output, "{}", repr.style(colors::primitive()))?;
394 writeln!(output, "{}", ")]".style(colors::attribute()))?;
395 }
396
397 write_facet_attrs_colored(shape, output)?;
399
400 if config.show_third_party_attrs {
402 write_third_party_attrs_colored(shape.attributes, output, "")?;
403 }
404
405 write!(output, "{} ", "enum".style(colors::keyword()))?;
407 write!(
408 output,
409 "{}",
410 shape.type_identifier.style(colors::type_name())
411 )?;
412 writeln!(output, " {}", "{".style(colors::punctuation()))?;
413
414 for (vi, variant) in enum_type.variants.iter().enumerate() {
415 if vi > 0 {
417 writeln!(output)?;
418 }
419 if config.show_doc_comments {
421 write_doc_comments_colored(variant.doc, output, " ")?;
422 }
423 if config.show_third_party_attrs {
425 write_variant_third_party_attrs_colored(variant, output, " ")?;
426 }
427
428 match variant.data.kind {
429 StructKind::Unit => {
430 write!(output, " {}", variant.name.style(colors::type_name()))?;
431 writeln!(output, "{}", ",".style(colors::punctuation()))?;
432 }
433 StructKind::Tuple | StructKind::TupleStruct => {
434 write!(output, " {}", variant.name.style(colors::type_name()))?;
435 write!(output, "{}", "(".style(colors::punctuation()))?;
436 for (i, field) in variant.data.fields.iter().enumerate() {
437 if i > 0 {
438 write!(output, "{} ", ",".style(colors::punctuation()))?;
439 }
440 write_type_name_colored(field.shape(), output)?;
441 }
442 write!(output, "{}", ")".style(colors::punctuation()))?;
443 writeln!(output, "{}", ",".style(colors::punctuation()))?;
444 }
445 StructKind::Struct => {
446 write!(output, " {}", variant.name.style(colors::type_name()))?;
447 writeln!(output, " {}", "{".style(colors::punctuation()))?;
448 for (fi, field) in variant.data.fields.iter().enumerate() {
449 if fi > 0 {
451 writeln!(output)?;
452 }
453 if config.show_doc_comments {
455 write_doc_comments_colored(field.doc, output, " ")?;
456 }
457 if config.show_third_party_attrs {
459 write_field_third_party_attrs_colored(field, output, " ")?;
460 }
461 write!(output, " {}", field.name.style(colors::field_name()))?;
462 write!(output, "{} ", ":".style(colors::punctuation()))?;
463 write_type_name_colored(field.shape(), output)?;
464 writeln!(output, "{}", ",".style(colors::punctuation()))?;
465 }
466 write!(output, " {}", "}".style(colors::punctuation()))?;
467 writeln!(output, "{}", ",".style(colors::punctuation()))?;
468 }
469 }
470 }
471
472 write!(output, "{}", "}".style(colors::punctuation()))?;
473 Ok(())
474}
475
476fn write_facet_attrs_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
477 let mut attrs: Vec<String> = Vec::new();
478
479 if let Some(tag) = shape.get_tag_attr() {
480 if let Some(content) = shape.get_content_attr() {
481 attrs.push(alloc::format!(
482 "{}{}{}{}{}{}{}{}{}",
483 "tag".style(colors::attribute_content()),
484 " = ".style(colors::punctuation()),
485 "\"".style(colors::string()),
486 tag.style(colors::string()),
487 "\"".style(colors::string()),
488 ", ".style(colors::punctuation()),
489 "content".style(colors::attribute_content()),
490 " = ".style(colors::punctuation()),
491 format!("\"{content}\"").style(colors::string()),
492 ));
493 } else {
494 attrs.push(alloc::format!(
495 "{}{}{}",
496 "tag".style(colors::attribute_content()),
497 " = ".style(colors::punctuation()),
498 format!("\"{tag}\"").style(colors::string()),
499 ));
500 }
501 }
502
503 if shape.is_untagged() {
504 attrs.push(alloc::format!(
505 "{}",
506 "untagged".style(colors::attribute_content())
507 ));
508 }
509
510 if shape.has_deny_unknown_fields_attr() {
511 attrs.push(alloc::format!(
512 "{}",
513 "deny_unknown_fields".style(colors::attribute_content())
514 ));
515 }
516
517 if !attrs.is_empty() {
518 write!(output, "{}", "#[".style(colors::attribute()))?;
519 write!(output, "{}", "facet".style(colors::attribute_content()))?;
520 write!(output, "{}", "(".style(colors::attribute()))?;
521 write!(
522 output,
523 "{}",
524 attrs.join(&format!("{}", ", ".style(colors::punctuation())))
525 )?;
526 writeln!(output, "{}", ")]".style(colors::attribute()))?;
527 }
528
529 Ok(())
530}
531
532fn write_doc_comments_colored(
534 doc: &[&str],
535 output: &mut String,
536 indent: &str,
537) -> core::fmt::Result {
538 for line in doc {
539 write!(output, "{indent}")?;
540 writeln!(output, "{}", format!("///{line}").style(colors::comment()))?;
541 }
542 Ok(())
543}
544
545fn write_third_party_attrs_colored(
548 attributes: &[Attr],
549 output: &mut String,
550 indent: &str,
551) -> core::fmt::Result {
552 let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
554 for attr in attributes {
555 if let Some(ns) = attr.ns {
556 by_namespace.entry(ns).or_default().push(attr.key);
557 }
558 }
559
560 for (ns, keys) in by_namespace {
562 write!(output, "{indent}")?;
563 write!(output, "{}", "#[".style(colors::attribute()))?;
564 write!(output, "{}", "facet".style(colors::attribute_content()))?;
565 write!(output, "{}", "(".style(colors::attribute()))?;
566
567 for (i, key) in keys.iter().enumerate() {
568 if i > 0 {
569 write!(output, "{}", ", ".style(colors::punctuation()))?;
570 }
571 write!(output, "{}", ns.style(colors::attribute_content()))?;
572 write!(output, "{}", "::".style(colors::punctuation()))?;
573 write!(output, "{}", key.style(colors::attribute_content()))?;
574 }
575
576 write!(output, "{}", ")".style(colors::attribute()))?;
577 writeln!(output, "{}", "]".style(colors::attribute()))?;
578 }
579 Ok(())
580}
581
582fn write_field_third_party_attrs_colored(
584 field: &Field,
585 output: &mut String,
586 indent: &str,
587) -> core::fmt::Result {
588 write_third_party_attrs_colored(field.attributes, output, indent)
589}
590
591fn write_variant_third_party_attrs_colored(
593 variant: &Variant,
594 output: &mut String,
595 indent: &str,
596) -> core::fmt::Result {
597 write_third_party_attrs_colored(variant.attributes, output, indent)
598}
599
600fn write_type_name_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
601 match shape.def {
602 Def::Scalar => {
603 let id = shape.type_identifier;
605 if is_primitive_type(id) {
606 write!(output, "{}", id.style(colors::primitive()))?;
607 } else {
608 write!(output, "{}", id.style(colors::type_name()))?;
609 }
610 }
611 Def::Pointer(_) => {
612 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
613 && let Def::Array(array_def) = r.target.def
614 {
615 write!(output, "{}", "&[".style(colors::punctuation()))?;
616 write_type_name_colored(array_def.t, output)?;
617 write!(
618 output,
619 "{}{}{}",
620 "; ".style(colors::punctuation()),
621 array_def.n.style(colors::primitive()),
622 "]".style(colors::punctuation())
623 )?;
624 return Ok(());
625 }
626 write!(
627 output,
628 "{}",
629 shape.type_identifier.style(colors::type_name())
630 )?;
631 }
632 Def::List(list_def) => {
633 write!(output, "{}", "Vec".style(colors::container()))?;
634 write!(output, "{}", "<".style(colors::punctuation()))?;
635 write_type_name_colored(list_def.t, output)?;
636 write!(output, "{}", ">".style(colors::punctuation()))?;
637 }
638 Def::Array(array_def) => {
639 write!(output, "{}", "[".style(colors::punctuation()))?;
640 write_type_name_colored(array_def.t, output)?;
641 write!(
642 output,
643 "{}{}{}",
644 "; ".style(colors::punctuation()),
645 array_def.n.style(colors::primitive()),
646 "]".style(colors::punctuation())
647 )?;
648 }
649 Def::Map(map_def) => {
650 let map_name = if shape.type_identifier.contains("BTreeMap") {
651 "BTreeMap"
652 } else {
653 "HashMap"
654 };
655 write!(output, "{}", map_name.style(colors::container()))?;
656 write!(output, "{}", "<".style(colors::punctuation()))?;
657 write_type_name_colored(map_def.k, output)?;
658 write!(output, "{} ", ",".style(colors::punctuation()))?;
659 write_type_name_colored(map_def.v, output)?;
660 write!(output, "{}", ">".style(colors::punctuation()))?;
661 }
662 Def::Option(option_def) => {
663 write!(output, "{}", "Option".style(colors::container()))?;
664 write!(output, "{}", "<".style(colors::punctuation()))?;
665 write_type_name_colored(option_def.t, output)?;
666 write!(output, "{}", ">".style(colors::punctuation()))?;
667 }
668 _ => {
669 let id = shape.type_identifier;
670 if is_primitive_type(id) {
671 write!(output, "{}", id.style(colors::primitive()))?;
672 } else {
673 write!(output, "{}", id.style(colors::type_name()))?;
674 }
675 }
676 }
677 Ok(())
678}
679
680fn is_primitive_type(id: &str) -> bool {
682 matches!(
683 id,
684 "u8" | "u16"
685 | "u32"
686 | "u64"
687 | "u128"
688 | "usize"
689 | "i8"
690 | "i16"
691 | "i32"
692 | "i64"
693 | "i128"
694 | "isize"
695 | "f32"
696 | "f64"
697 | "bool"
698 | "char"
699 | "str"
700 | "&str"
701 | "String"
702 )
703}
704
705struct SpanTrackingContext {
707 output: String,
708 spans: BTreeMap<Path, FieldSpan>,
709 current_type: Option<&'static str>,
711}
712
713impl SpanTrackingContext {
714 fn new() -> Self {
715 Self {
716 output: String::new(),
717 spans: BTreeMap::new(),
718 current_type: None,
719 }
720 }
721
722 fn len(&self) -> usize {
723 self.output.len()
724 }
725
726 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
727 self.spans.insert(
728 path,
729 FieldSpan {
730 key: key_span,
731 value: value_span,
732 },
733 );
734 }
735}
736
737fn format_shape_into_with_spans(shape: &Shape, ctx: &mut SpanTrackingContext) -> core::fmt::Result {
739 let mut printed: BTreeSet<&'static str> = BTreeSet::new();
741 let mut queue: Vec<&Shape> = Vec::new();
743
744 queue.push(shape);
746
747 while let Some(current) = queue.pop() {
748 if !printed.insert(current.type_identifier) {
750 continue;
751 }
752
753 if printed.len() > 1 {
755 writeln!(ctx.output)?;
756 writeln!(ctx.output)?;
757 }
758
759 match current.def {
762 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
763 printed.remove(current.type_identifier);
765 continue;
766 }
767 _ => {}
768 }
769
770 match ¤t.ty {
772 Type::User(user_type) => match user_type {
773 UserType::Struct(struct_type) => {
774 ctx.current_type = Some(current.type_identifier);
775 format_struct_with_spans(current, struct_type, ctx)?;
776 ctx.current_type = None;
777 collect_nested_types(struct_type, &mut queue);
779 }
780 UserType::Enum(enum_type) => {
781 ctx.current_type = Some(current.type_identifier);
782 format_enum_with_spans(current, enum_type, ctx)?;
783 ctx.current_type = None;
784 for variant in enum_type.variants {
786 collect_nested_types(&variant.data, &mut queue);
787 }
788 }
789 UserType::Union(_) | UserType::Opaque => {
790 printed.remove(current.type_identifier);
793 }
794 },
795 _ => {
796 printed.remove(current.type_identifier);
798 }
799 }
800 }
801 Ok(())
802}
803
804fn format_struct_with_spans(
805 shape: &Shape,
806 struct_type: &StructType,
807 ctx: &mut SpanTrackingContext,
808) -> core::fmt::Result {
809 let type_start = ctx.len();
811
812 writeln!(ctx.output, "#[derive(Facet)]")?;
814
815 write_facet_attrs(shape, &mut ctx.output)?;
817
818 match struct_type.kind {
820 StructKind::Struct => {
821 writeln!(ctx.output, "struct {} {{", shape.type_identifier)?;
822 for field in struct_type.fields {
823 write!(ctx.output, " ")?;
824 let key_start = ctx.len();
826 write!(ctx.output, "{}", field.name)?;
827 let key_end = ctx.len();
828 write!(ctx.output, ": ")?;
829 let value_start = ctx.len();
831 write_type_name(field.shape(), &mut ctx.output)?;
832 let value_end = ctx.len();
833 ctx.record_field_span(
834 vec![PathSegment::Field(Cow::Borrowed(field.name))],
835 (key_start, key_end),
836 (value_start, value_end),
837 );
838 writeln!(ctx.output, ",")?;
839 }
840 write!(ctx.output, "}}")?;
841 }
842 StructKind::Tuple | StructKind::TupleStruct => {
843 write!(ctx.output, "struct {}(", shape.type_identifier)?;
844 for (i, field) in struct_type.fields.iter().enumerate() {
845 if i > 0 {
846 write!(ctx.output, ", ")?;
847 }
848 let type_start = ctx.len();
850 write_type_name(field.shape(), &mut ctx.output)?;
851 let type_end = ctx.len();
852 let field_name = if !field.name.is_empty() {
854 field.name
855 } else {
856 continue;
858 };
859 ctx.record_field_span(
860 vec![PathSegment::Field(Cow::Borrowed(field_name))],
861 (type_start, type_end), (type_start, type_end),
863 );
864 }
865 write!(ctx.output, ");")?;
866 }
867 StructKind::Unit => {
868 write!(ctx.output, "struct {};", shape.type_identifier)?;
869 }
870 }
871
872 let type_end = ctx.len();
874 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
875
876 Ok(())
877}
878
879fn format_enum_with_spans(
880 shape: &Shape,
881 enum_type: &EnumType,
882 ctx: &mut SpanTrackingContext,
883) -> core::fmt::Result {
884 let type_start = ctx.len();
886
887 writeln!(ctx.output, "#[derive(Facet)]")?;
889
890 let repr_str = match enum_type.enum_repr {
892 EnumRepr::RustNPO => None,
893 EnumRepr::U8 => Some("u8"),
894 EnumRepr::U16 => Some("u16"),
895 EnumRepr::U32 => Some("u32"),
896 EnumRepr::U64 => Some("u64"),
897 EnumRepr::USize => Some("usize"),
898 EnumRepr::I8 => Some("i8"),
899 EnumRepr::I16 => Some("i16"),
900 EnumRepr::I32 => Some("i32"),
901 EnumRepr::I64 => Some("i64"),
902 EnumRepr::ISize => Some("isize"),
903 };
904
905 if let Some(repr) = repr_str {
906 writeln!(ctx.output, "#[repr({repr})]")?;
907 }
908
909 write_facet_attrs(shape, &mut ctx.output)?;
911
912 writeln!(ctx.output, "enum {} {{", shape.type_identifier)?;
914
915 for variant in enum_type.variants {
916 match variant.data.kind {
917 StructKind::Unit => {
918 write!(ctx.output, " ")?;
919 let name_start = ctx.len();
921 write!(ctx.output, "{}", variant.name)?;
922 let name_end = ctx.len();
923 ctx.record_field_span(
924 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
925 (name_start, name_end),
926 (name_start, name_end),
927 );
928 writeln!(ctx.output, ",")?;
929 }
930 StructKind::Tuple | StructKind::TupleStruct => {
931 write!(ctx.output, " ")?;
932 let variant_name_start = ctx.len();
933 write!(ctx.output, "{}", variant.name)?;
934 let variant_name_end = ctx.len();
935 write!(ctx.output, "(")?;
936 let tuple_start = ctx.len();
937 for (i, field) in variant.data.fields.iter().enumerate() {
938 if i > 0 {
939 write!(ctx.output, ", ")?;
940 }
941 let type_start = ctx.len();
942 write_type_name(field.shape(), &mut ctx.output)?;
943 let type_end = ctx.len();
944 if !field.name.is_empty() {
946 ctx.record_field_span(
947 vec![
948 PathSegment::Variant(Cow::Borrowed(variant.name)),
949 PathSegment::Field(Cow::Borrowed(field.name)),
950 ],
951 (type_start, type_end),
952 (type_start, type_end),
953 );
954 }
955 }
956 write!(ctx.output, ")")?;
957 let tuple_end = ctx.len();
958 ctx.record_field_span(
960 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
961 (variant_name_start, variant_name_end),
962 (tuple_start, tuple_end),
963 );
964 writeln!(ctx.output, ",")?;
965 }
966 StructKind::Struct => {
967 write!(ctx.output, " ")?;
968 let variant_name_start = ctx.len();
969 write!(ctx.output, "{}", variant.name)?;
970 let variant_name_end = ctx.len();
971 writeln!(ctx.output, " {{")?;
972 let struct_start = ctx.len();
973 for field in variant.data.fields {
974 write!(ctx.output, " ")?;
975 let key_start = ctx.len();
976 write!(ctx.output, "{}", field.name)?;
977 let key_end = ctx.len();
978 write!(ctx.output, ": ")?;
979 let value_start = ctx.len();
980 write_type_name(field.shape(), &mut ctx.output)?;
981 let value_end = ctx.len();
982 ctx.record_field_span(
983 vec![
984 PathSegment::Variant(Cow::Borrowed(variant.name)),
985 PathSegment::Field(Cow::Borrowed(field.name)),
986 ],
987 (key_start, key_end),
988 (value_start, value_end),
989 );
990 writeln!(ctx.output, ",")?;
991 }
992 write!(ctx.output, " }}")?;
993 let struct_end = ctx.len();
994 ctx.record_field_span(
996 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
997 (variant_name_start, variant_name_end),
998 (struct_start, struct_end),
999 );
1000 writeln!(ctx.output, ",")?;
1001 }
1002 }
1003 }
1004
1005 write!(ctx.output, "}}")?;
1006
1007 let type_end = ctx.len();
1009 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
1010
1011 Ok(())
1012}
1013
1014fn collect_nested_types<'a>(struct_type: &'a StructType, queue: &mut Vec<&'a Shape>) {
1016 for field in struct_type.fields {
1017 collect_from_shape(field.shape(), queue);
1018 }
1019}
1020
1021fn collect_from_shape<'a>(shape: &'a Shape, queue: &mut Vec<&'a Shape>) {
1023 match shape.def {
1024 Def::List(list_def) => collect_from_shape(list_def.t, queue),
1025 Def::Array(array_def) => collect_from_shape(array_def.t, queue),
1026 Def::Map(map_def) => {
1027 collect_from_shape(map_def.k, queue);
1028 collect_from_shape(map_def.v, queue);
1029 }
1030 Def::Option(option_def) => collect_from_shape(option_def.t, queue),
1031 _ => {
1032 if let Type::User(UserType::Struct(_) | UserType::Enum(_)) = &shape.ty {
1034 queue.push(shape);
1035 }
1036 }
1037 }
1038}
1039
1040fn write_facet_attrs(shape: &Shape, output: &mut String) -> core::fmt::Result {
1042 let mut attrs: Vec<String> = Vec::new();
1043
1044 if let Some(tag) = shape.get_tag_attr() {
1045 if let Some(content) = shape.get_content_attr() {
1046 attrs.push(alloc::format!("tag = \"{tag}\", content = \"{content}\""));
1047 } else {
1048 attrs.push(alloc::format!("tag = \"{tag}\""));
1049 }
1050 }
1051
1052 if shape.is_untagged() {
1053 attrs.push("untagged".into());
1054 }
1055
1056 if shape.has_deny_unknown_fields_attr() {
1057 attrs.push("deny_unknown_fields".into());
1058 }
1059
1060 if !attrs.is_empty() {
1061 writeln!(output, "#[facet({})]", attrs.join(", "))?;
1062 }
1063
1064 Ok(())
1065}
1066
1067fn write_type_name(shape: &Shape, output: &mut String) -> core::fmt::Result {
1068 match shape.def {
1069 Def::Scalar => {
1070 write!(output, "{}", shape.type_identifier)?;
1071 }
1072 Def::Pointer(_) => {
1073 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
1074 && let Def::Array(array_def) = r.target.def
1075 {
1076 write!(output, "&[")?;
1077 write_type_name(array_def.t, output)?;
1078 write!(output, "; {}]", array_def.n)?;
1079 return Ok(());
1080 }
1081 write!(output, "{}", shape.type_identifier)?;
1082 }
1083 Def::List(list_def) => {
1084 write!(output, "Vec<")?;
1085 write_type_name(list_def.t, output)?;
1086 write!(output, ">")?;
1087 }
1088 Def::Array(array_def) => {
1089 write!(output, "[")?;
1090 write_type_name(array_def.t, output)?;
1091 write!(output, "; {}]", array_def.n)?;
1092 }
1093 Def::Map(map_def) => {
1094 let map_name = if shape.type_identifier.contains("BTreeMap") {
1095 "BTreeMap"
1096 } else {
1097 "HashMap"
1098 };
1099 write!(output, "{map_name}<")?;
1100 write_type_name(map_def.k, output)?;
1101 write!(output, ", ")?;
1102 write_type_name(map_def.v, output)?;
1103 write!(output, ">")?;
1104 }
1105 Def::Option(option_def) => {
1106 write!(output, "Option<")?;
1107 write_type_name(option_def.t, output)?;
1108 write!(output, ">")?;
1109 }
1110 _ => {
1111 write!(output, "{}", shape.type_identifier)?;
1112 }
1113 }
1114 Ok(())
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119 use super::*;
1120 use facet::Facet;
1121
1122 #[test]
1123 fn test_simple_struct() {
1124 #[derive(Facet)]
1125 struct Simple {
1126 name: String,
1127 count: u32,
1128 }
1129
1130 let output = format_shape(Simple::SHAPE);
1131 assert!(output.contains("struct Simple"));
1132 assert!(output.contains("name: String"));
1133 assert!(output.contains("count: u32"));
1134 }
1135
1136 #[test]
1137 fn test_enum_with_tag() {
1138 #[derive(Facet)]
1139 #[repr(C)]
1140 #[facet(tag = "type")]
1141 #[allow(dead_code)]
1142 enum Tagged {
1143 A { x: i32 },
1144 B { y: String },
1145 }
1146
1147 let output = format_shape(Tagged::SHAPE);
1148 assert!(output.contains("enum Tagged"));
1149 assert!(output.contains("#[facet(tag = \"type\")]"));
1150 }
1151
1152 #[test]
1153 fn test_nested_types() {
1154 #[derive(Facet)]
1155 #[allow(dead_code)]
1156 struct Inner {
1157 value: i32,
1158 }
1159
1160 #[derive(Facet)]
1161 #[allow(dead_code)]
1162 struct Outer {
1163 inner: Inner,
1164 name: String,
1165 }
1166
1167 let output = format_shape(Outer::SHAPE);
1168 assert!(output.contains("struct Outer"), "Missing Outer: {output}");
1170 assert!(
1171 output.contains("inner: Inner"),
1172 "Missing inner field: {output}"
1173 );
1174 assert!(
1175 output.contains("struct Inner"),
1176 "Missing Inner definition: {output}"
1177 );
1178 assert!(
1179 output.contains("value: i32"),
1180 "Missing value field: {output}"
1181 );
1182 }
1183
1184 #[test]
1185 fn test_nested_in_vec() {
1186 #[derive(Facet)]
1187 #[allow(dead_code)]
1188 struct Item {
1189 id: u32,
1190 }
1191
1192 #[derive(Facet)]
1193 #[allow(dead_code)]
1194 struct Container {
1195 items: Vec<Item>,
1196 }
1197
1198 let output = format_shape(Container::SHAPE);
1199 assert!(
1201 output.contains("struct Container"),
1202 "Missing Container: {output}"
1203 );
1204 assert!(
1205 output.contains("items: Vec<Item>"),
1206 "Missing items field: {output}"
1207 );
1208 assert!(
1209 output.contains("struct Item"),
1210 "Missing Item definition: {output}"
1211 );
1212 }
1213
1214 #[test]
1215 fn test_format_shape_with_spans() {
1216 #[derive(Facet)]
1217 #[allow(dead_code)]
1218 struct Config {
1219 name: String,
1220 max_retries: u8,
1221 enabled: bool,
1222 }
1223
1224 let result = format_shape_with_spans(Config::SHAPE);
1225
1226 let name_path = vec![PathSegment::Field(Cow::Borrowed("name"))];
1228 let retries_path = vec![PathSegment::Field(Cow::Borrowed("max_retries"))];
1229 let enabled_path = vec![PathSegment::Field(Cow::Borrowed("enabled"))];
1230
1231 assert!(
1232 result.spans.contains_key(&name_path),
1233 "Missing span for 'name' field. Spans: {:?}",
1234 result.spans
1235 );
1236 assert!(
1237 result.spans.contains_key(&retries_path),
1238 "Missing span for 'max_retries' field. Spans: {:?}",
1239 result.spans
1240 );
1241 assert!(
1242 result.spans.contains_key(&enabled_path),
1243 "Missing span for 'enabled' field. Spans: {:?}",
1244 result.spans
1245 );
1246
1247 let field_span = &result.spans[&retries_path];
1249 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1250 assert_eq!(spanned_text, "u8", "Expected 'u8', got '{spanned_text}'");
1251 }
1252
1253 #[test]
1254 fn test_format_enum_with_spans() {
1255 #[derive(Facet)]
1256 #[repr(u8)]
1257 #[allow(dead_code)]
1258 enum Status {
1259 Active,
1260 Pending,
1261 Error { code: i32, message: String },
1262 }
1263
1264 let result = format_shape_with_spans(Status::SHAPE);
1265
1266 let active_path = vec![PathSegment::Variant(Cow::Borrowed("Active"))];
1268 let error_path = vec![PathSegment::Variant(Cow::Borrowed("Error"))];
1269 let error_code_path = vec![
1270 PathSegment::Variant(Cow::Borrowed("Error")),
1271 PathSegment::Field(Cow::Borrowed("code")),
1272 ];
1273
1274 assert!(
1275 result.spans.contains_key(&active_path),
1276 "Missing span for 'Active' variant. Spans: {:?}",
1277 result.spans
1278 );
1279 assert!(
1280 result.spans.contains_key(&error_path),
1281 "Missing span for 'Error' variant. Spans: {:?}",
1282 result.spans
1283 );
1284 assert!(
1285 result.spans.contains_key(&error_code_path),
1286 "Missing span for 'Error.code' field. Spans: {:?}",
1287 result.spans
1288 );
1289
1290 let field_span = &result.spans[&error_code_path];
1292 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1293 assert_eq!(spanned_text, "i32", "Expected 'i32', got '{spanned_text}'");
1294 }
1295
1296 #[test]
1297 fn test_format_with_doc_comments() {
1298 #[derive(Facet)]
1300 #[allow(dead_code)]
1301 struct Config {
1302 name: String,
1304 max_retries: u8,
1306 }
1307
1308 let output = format_shape(Config::SHAPE);
1310 assert!(
1311 output.contains("/// A configuration struct"),
1312 "Should contain struct doc comment: {output}"
1313 );
1314 assert!(
1315 output.contains("/// The name of the configuration"),
1316 "Should contain field doc comment: {output}"
1317 );
1318 assert!(
1319 output.contains("/// Maximum number of retries"),
1320 "Should contain field doc comment: {output}"
1321 );
1322
1323 let config = ShapeFormatConfig::new();
1325 let output_without = format_shape_with_config(Config::SHAPE, &config);
1326 assert!(
1327 !output_without.contains("///"),
1328 "Should not contain doc comments when disabled: {output_without}"
1329 );
1330 }
1331
1332 #[test]
1333 fn test_format_enum_with_doc_comments() {
1334 #[derive(Facet)]
1336 #[repr(u8)]
1337 #[allow(dead_code)]
1338 enum Status {
1339 Active,
1341 Error {
1343 code: i32,
1345 },
1346 }
1347
1348 let config = ShapeFormatConfig::new().with_doc_comments();
1349 let output = format_shape_with_config(Status::SHAPE, &config);
1350
1351 assert!(
1352 output.contains("/// Status of an operation"),
1353 "Should contain enum doc comment: {output}"
1354 );
1355 assert!(
1356 output.contains("/// The operation is active"),
1357 "Should contain variant doc comment: {output}"
1358 );
1359 assert!(
1360 output.contains("/// The operation failed"),
1361 "Should contain variant doc comment: {output}"
1362 );
1363 assert!(
1364 output.contains("/// Error code"),
1365 "Should contain variant field doc comment: {output}"
1366 );
1367 }
1368}