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 const fn keyword() -> Style {
26 Style::new().fg_rgb::<187, 154, 247>()
27 }
28
29 pub const fn type_name() -> Style {
31 Style::new().fg_rgb::<192, 202, 245>()
32 }
33
34 pub const fn field_name() -> Style {
36 Style::new().fg_rgb::<125, 207, 255>()
37 }
38
39 pub const fn primitive() -> Style {
41 Style::new().fg_rgb::<115, 218, 202>()
42 }
43
44 pub const fn punctuation() -> Style {
46 Style::new().fg_rgb::<154, 165, 206>()
47 }
48
49 pub const fn attribute() -> Style {
51 Style::new().fg_rgb::<137, 221, 255>()
52 }
53
54 pub const fn attribute_content() -> Style {
56 Style::new().fg_rgb::<122, 162, 247>()
57 }
58
59 pub const fn string() -> Style {
61 Style::new().fg_rgb::<158, 206, 106>()
62 }
63
64 pub const fn container() -> Style {
66 Style::new().fg_rgb::<255, 158, 100>()
67 }
68
69 pub const 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 pub expand_nested_types: bool,
84}
85
86impl ShapeFormatConfig {
87 pub fn new() -> Self {
89 Self {
90 expand_nested_types: true,
91 ..Self::default()
92 }
93 }
94
95 pub const fn with_doc_comments(mut self) -> Self {
97 self.show_doc_comments = true;
98 self
99 }
100
101 pub const fn with_third_party_attrs(mut self) -> Self {
103 self.show_third_party_attrs = true;
104 self
105 }
106
107 pub const fn with_all_metadata(mut self) -> Self {
109 self.show_doc_comments = true;
110 self.show_third_party_attrs = true;
111 self
112 }
113
114 pub const fn without_nested_types(mut self) -> Self {
116 self.expand_nested_types = false;
117 self
118 }
119}
120
121#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
123pub enum PathSegment {
124 Field(Cow<'static, str>),
126 Variant(Cow<'static, str>),
128 Index(usize),
130 Key(Cow<'static, str>),
132}
133
134pub type Path = Vec<PathSegment>;
136
137pub type Span = (usize, usize);
139
140#[derive(Clone, Debug, Default, PartialEq, Eq)]
142pub struct FieldSpan {
143 pub key: Span,
145 pub value: Span,
147}
148
149#[derive(Debug)]
151pub struct FormattedShape {
152 pub text: String,
154 pub spans: BTreeMap<Path, FieldSpan>,
156 pub type_name_span: Option<Span>,
158}
159
160pub fn strip_ansi(s: &str) -> String {
162 let mut result = String::with_capacity(s.len());
163 let mut chars = s.chars().peekable();
164
165 while let Some(c) = chars.next() {
166 if c == '\x1b' {
167 if chars.peek() == Some(&'[') {
169 chars.next(); while let Some(&next) = chars.peek() {
172 chars.next();
173 if next.is_ascii_alphabetic() {
174 break;
175 }
176 }
177 }
178 } else {
179 result.push(c);
180 }
181 }
182 result
183}
184
185pub fn format_shape(shape: &Shape) -> String {
189 strip_ansi(&format_shape_colored(shape))
190}
191
192pub fn format_shape_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
194 strip_ansi(&format_shape_colored_with_config(shape, config))
195}
196
197pub fn format_shape_with_spans(shape: &Shape) -> FormattedShape {
202 format_shape_with_spans_and_config(shape, &ShapeFormatConfig::default().with_all_metadata())
203}
204
205pub fn format_shape_with_spans_and_config(
208 shape: &Shape,
209 config: &ShapeFormatConfig,
210) -> FormattedShape {
211 let mut ctx = SpanTrackingContext::new(config);
212 format_shape_into_with_spans(shape, &mut ctx).expect("Formatting failed");
213 FormattedShape {
214 text: ctx.output,
215 spans: ctx.spans,
216 type_name_span: ctx.type_name_span,
217 }
218}
219
220pub fn format_shape_colored(shape: &Shape) -> String {
224 format_shape_colored_with_config(shape, &ShapeFormatConfig::default().with_all_metadata())
225}
226
227pub fn format_shape_colored_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
229 let mut output = String::new();
230 format_shape_colored_into_with_config(shape, &mut output, config).expect("Formatting failed");
231 output
232}
233
234pub fn format_shape_colored_into(shape: &Shape, output: &mut String) -> core::fmt::Result {
236 format_shape_colored_into_with_config(shape, output, &ShapeFormatConfig::default())
237}
238
239pub fn format_shape_colored_into_with_config(
241 shape: &Shape,
242 output: &mut String,
243 config: &ShapeFormatConfig,
244) -> core::fmt::Result {
245 let mut printed: BTreeSet<&'static str> = BTreeSet::new();
246 let mut queue: Vec<&Shape> = Vec::new();
247 queue.push(shape);
248
249 while let Some(current) = queue.pop() {
250 if !printed.insert(current.type_identifier) {
251 continue;
252 }
253
254 if printed.len() > 1 {
255 writeln!(output)?;
256 writeln!(output)?;
257 }
258
259 match current.def {
260 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
261 printed.remove(current.type_identifier);
262 continue;
263 }
264 _ => {}
265 }
266
267 match ¤t.ty {
268 Type::User(user_type) => match user_type {
269 UserType::Struct(struct_type) => {
270 format_struct_colored(current, struct_type, output, config)?;
271 collect_nested_types(struct_type, &mut queue);
272 }
273 UserType::Enum(enum_type) => {
274 format_enum_colored(current, enum_type, output, config)?;
275 for variant in enum_type.variants {
276 collect_nested_types(&variant.data, &mut queue);
277 }
278 }
279 UserType::Union(_) | UserType::Opaque => {
280 printed.remove(current.type_identifier);
281 }
282 },
283 _ => {
284 printed.remove(current.type_identifier);
285 }
286 }
287 }
288 Ok(())
289}
290
291fn format_struct_colored(
292 shape: &Shape,
293 struct_type: &StructType,
294 output: &mut String,
295 config: &ShapeFormatConfig,
296) -> core::fmt::Result {
297 if config.show_doc_comments {
299 write_doc_comments_colored(shape.doc, output, "")?;
300 }
301
302 write!(output, "{}", "#[".style(colors::attribute()))?;
304 write!(output, "{}", "derive".style(colors::attribute_content()))?;
305 write!(output, "{}", "(".style(colors::attribute()))?;
306 write!(output, "{}", "Facet".style(colors::attribute_content()))?;
307 writeln!(output, "{}", ")]".style(colors::attribute()))?;
308
309 write_facet_attrs_colored(shape, output)?;
311
312 if config.show_third_party_attrs {
314 write_third_party_attrs_colored(shape.attributes, output, "")?;
315 }
316
317 match struct_type.kind {
318 StructKind::Struct => {
319 write!(output, "{} ", "struct".style(colors::keyword()))?;
320 write!(
321 output,
322 "{}",
323 shape.type_identifier.style(colors::type_name())
324 )?;
325 writeln!(output, " {}", "{".style(colors::punctuation()))?;
326
327 for (i, field) in struct_type.fields.iter().enumerate() {
328 if i > 0 {
330 writeln!(output)?;
331 }
332 if config.show_doc_comments {
334 write_doc_comments_colored(field.doc, output, " ")?;
335 }
336 if config.show_third_party_attrs {
338 write_field_third_party_attrs_colored(field, output, " ")?;
339 }
340 write!(output, " {}", field.name.style(colors::field_name()))?;
341 write!(output, "{} ", ":".style(colors::punctuation()))?;
342 write_type_name_colored(field.shape(), output)?;
343 writeln!(output, "{}", ",".style(colors::punctuation()))?;
344 }
345 write!(output, "{}", "}".style(colors::punctuation()))?;
346 }
347 StructKind::Tuple | StructKind::TupleStruct => {
348 write!(output, "{} ", "struct".style(colors::keyword()))?;
349 write!(
350 output,
351 "{}",
352 shape.type_identifier.style(colors::type_name())
353 )?;
354 write!(output, "{}", "(".style(colors::punctuation()))?;
355 for (i, field) in struct_type.fields.iter().enumerate() {
356 if i > 0 {
357 write!(output, "{} ", ",".style(colors::punctuation()))?;
358 }
359 write_type_name_colored(field.shape(), output)?;
360 }
361 write!(
362 output,
363 "{}{}",
364 ")".style(colors::punctuation()),
365 ";".style(colors::punctuation())
366 )?;
367 }
368 StructKind::Unit => {
369 write!(output, "{} ", "struct".style(colors::keyword()))?;
370 write!(
371 output,
372 "{}",
373 shape.type_identifier.style(colors::type_name())
374 )?;
375 write!(output, "{}", ";".style(colors::punctuation()))?;
376 }
377 }
378 Ok(())
379}
380
381fn format_enum_colored(
382 shape: &Shape,
383 enum_type: &EnumType,
384 output: &mut String,
385 config: &ShapeFormatConfig,
386) -> core::fmt::Result {
387 if config.show_doc_comments {
389 write_doc_comments_colored(shape.doc, output, "")?;
390 }
391
392 write!(output, "{}", "#[".style(colors::attribute()))?;
394 write!(output, "{}", "derive".style(colors::attribute_content()))?;
395 write!(output, "{}", "(".style(colors::attribute()))?;
396 write!(output, "{}", "Facet".style(colors::attribute_content()))?;
397 writeln!(output, "{}", ")]".style(colors::attribute()))?;
398
399 let repr_str = match enum_type.enum_repr {
401 EnumRepr::RustNPO => None,
402 EnumRepr::U8 => Some("u8"),
403 EnumRepr::U16 => Some("u16"),
404 EnumRepr::U32 => Some("u32"),
405 EnumRepr::U64 => Some("u64"),
406 EnumRepr::USize => Some("usize"),
407 EnumRepr::I8 => Some("i8"),
408 EnumRepr::I16 => Some("i16"),
409 EnumRepr::I32 => Some("i32"),
410 EnumRepr::I64 => Some("i64"),
411 EnumRepr::ISize => Some("isize"),
412 };
413
414 if let Some(repr) = repr_str {
415 write!(output, "{}", "#[".style(colors::attribute()))?;
416 write!(output, "{}", "repr".style(colors::attribute_content()))?;
417 write!(output, "{}", "(".style(colors::attribute()))?;
418 write!(output, "{}", repr.style(colors::primitive()))?;
419 writeln!(output, "{}", ")]".style(colors::attribute()))?;
420 }
421
422 write_facet_attrs_colored(shape, output)?;
424
425 if config.show_third_party_attrs {
427 write_third_party_attrs_colored(shape.attributes, output, "")?;
428 }
429
430 write!(output, "{} ", "enum".style(colors::keyword()))?;
432 write!(
433 output,
434 "{}",
435 shape.type_identifier.style(colors::type_name())
436 )?;
437 writeln!(output, " {}", "{".style(colors::punctuation()))?;
438
439 for (vi, variant) in enum_type.variants.iter().enumerate() {
440 if vi > 0 {
442 writeln!(output)?;
443 }
444 if config.show_doc_comments {
446 write_doc_comments_colored(variant.doc, output, " ")?;
447 }
448 if config.show_third_party_attrs {
450 write_variant_third_party_attrs_colored(variant, output, " ")?;
451 }
452
453 match variant.data.kind {
454 StructKind::Unit => {
455 write!(output, " {}", variant.name.style(colors::type_name()))?;
456 writeln!(output, "{}", ",".style(colors::punctuation()))?;
457 }
458 StructKind::Tuple | StructKind::TupleStruct => {
459 write!(output, " {}", variant.name.style(colors::type_name()))?;
460 write!(output, "{}", "(".style(colors::punctuation()))?;
461 for (i, field) in variant.data.fields.iter().enumerate() {
462 if i > 0 {
463 write!(output, "{} ", ",".style(colors::punctuation()))?;
464 }
465 write_type_name_colored(field.shape(), output)?;
466 }
467 write!(output, "{}", ")".style(colors::punctuation()))?;
468 writeln!(output, "{}", ",".style(colors::punctuation()))?;
469 }
470 StructKind::Struct => {
471 write!(output, " {}", variant.name.style(colors::type_name()))?;
472 writeln!(output, " {}", "{".style(colors::punctuation()))?;
473 for (fi, field) in variant.data.fields.iter().enumerate() {
474 if fi > 0 {
476 writeln!(output)?;
477 }
478 if config.show_doc_comments {
480 write_doc_comments_colored(field.doc, output, " ")?;
481 }
482 if config.show_third_party_attrs {
484 write_field_third_party_attrs_colored(field, output, " ")?;
485 }
486 write!(output, " {}", field.name.style(colors::field_name()))?;
487 write!(output, "{} ", ":".style(colors::punctuation()))?;
488 write_type_name_colored(field.shape(), output)?;
489 writeln!(output, "{}", ",".style(colors::punctuation()))?;
490 }
491 write!(output, " {}", "}".style(colors::punctuation()))?;
492 writeln!(output, "{}", ",".style(colors::punctuation()))?;
493 }
494 }
495 }
496
497 write!(output, "{}", "}".style(colors::punctuation()))?;
498 Ok(())
499}
500
501fn write_facet_attrs_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
502 let mut attrs: Vec<String> = Vec::new();
503
504 if let Some(tag) = shape.get_tag_attr() {
505 if let Some(content) = shape.get_content_attr() {
506 attrs.push(alloc::format!(
507 "{}{}{}{}{}{}{}{}{}",
508 "tag".style(colors::attribute_content()),
509 " = ".style(colors::punctuation()),
510 "\"".style(colors::string()),
511 tag.style(colors::string()),
512 "\"".style(colors::string()),
513 ", ".style(colors::punctuation()),
514 "content".style(colors::attribute_content()),
515 " = ".style(colors::punctuation()),
516 format!("\"{content}\"").style(colors::string()),
517 ));
518 } else {
519 attrs.push(alloc::format!(
520 "{}{}{}",
521 "tag".style(colors::attribute_content()),
522 " = ".style(colors::punctuation()),
523 format!("\"{tag}\"").style(colors::string()),
524 ));
525 }
526 }
527
528 if shape.is_untagged() {
529 attrs.push(alloc::format!(
530 "{}",
531 "untagged".style(colors::attribute_content())
532 ));
533 }
534
535 if shape.has_deny_unknown_fields_attr() {
536 attrs.push(alloc::format!(
537 "{}",
538 "deny_unknown_fields".style(colors::attribute_content())
539 ));
540 }
541
542 if !attrs.is_empty() {
543 write!(output, "{}", "#[".style(colors::attribute()))?;
544 write!(output, "{}", "facet".style(colors::attribute_content()))?;
545 write!(output, "{}", "(".style(colors::attribute()))?;
546 write!(
547 output,
548 "{}",
549 attrs.join(&format!("{}", ", ".style(colors::punctuation())))
550 )?;
551 writeln!(output, "{}", ")]".style(colors::attribute()))?;
552 }
553
554 Ok(())
555}
556
557fn write_doc_comments_colored(
559 doc: &[&str],
560 output: &mut String,
561 indent: &str,
562) -> core::fmt::Result {
563 for line in doc {
564 write!(output, "{indent}")?;
565 writeln!(output, "{}", format!("///{line}").style(colors::comment()))?;
566 }
567 Ok(())
568}
569
570fn write_third_party_attrs_colored(
573 attributes: &[Attr],
574 output: &mut String,
575 indent: &str,
576) -> core::fmt::Result {
577 let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
579 for attr in attributes {
580 if let Some(ns) = attr.ns {
581 by_namespace.entry(ns).or_default().push(attr.key);
582 }
583 }
584
585 for (ns, keys) in by_namespace {
587 write!(output, "{indent}")?;
588 write!(output, "{}", "#[".style(colors::attribute()))?;
589 write!(output, "{}", "facet".style(colors::attribute_content()))?;
590 write!(output, "{}", "(".style(colors::attribute()))?;
591
592 for (i, key) in keys.iter().enumerate() {
593 if i > 0 {
594 write!(output, "{}", ", ".style(colors::punctuation()))?;
595 }
596 write!(output, "{}", ns.style(colors::attribute_content()))?;
597 write!(output, "{}", "::".style(colors::punctuation()))?;
598 write!(output, "{}", key.style(colors::attribute_content()))?;
599 }
600
601 write!(output, "{}", ")".style(colors::attribute()))?;
602 writeln!(output, "{}", "]".style(colors::attribute()))?;
603 }
604 Ok(())
605}
606
607fn write_field_third_party_attrs_colored(
609 field: &Field,
610 output: &mut String,
611 indent: &str,
612) -> core::fmt::Result {
613 write_third_party_attrs_colored(field.attributes, output, indent)
614}
615
616fn write_variant_third_party_attrs_colored(
618 variant: &Variant,
619 output: &mut String,
620 indent: &str,
621) -> core::fmt::Result {
622 write_third_party_attrs_colored(variant.attributes, output, indent)
623}
624
625fn write_type_name_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
626 match shape.def {
627 Def::Scalar => {
628 let id = shape.type_identifier;
630 if is_primitive_type(id) {
631 write!(output, "{}", id.style(colors::primitive()))?;
632 } else {
633 write!(output, "{}", id.style(colors::type_name()))?;
634 }
635 }
636 Def::Pointer(_) => {
637 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
638 && let Def::Array(array_def) = r.target.def
639 {
640 write!(output, "{}", "&[".style(colors::punctuation()))?;
641 write_type_name_colored(array_def.t, output)?;
642 write!(
643 output,
644 "{}{}{}",
645 "; ".style(colors::punctuation()),
646 array_def.n.style(colors::primitive()),
647 "]".style(colors::punctuation())
648 )?;
649 return Ok(());
650 }
651 write!(
652 output,
653 "{}",
654 shape.type_identifier.style(colors::type_name())
655 )?;
656 }
657 Def::List(list_def) => {
658 write!(output, "{}", "Vec".style(colors::container()))?;
659 write!(output, "{}", "<".style(colors::punctuation()))?;
660 write_type_name_colored(list_def.t, output)?;
661 write!(output, "{}", ">".style(colors::punctuation()))?;
662 }
663 Def::Array(array_def) => {
664 write!(output, "{}", "[".style(colors::punctuation()))?;
665 write_type_name_colored(array_def.t, output)?;
666 write!(
667 output,
668 "{}{}{}",
669 "; ".style(colors::punctuation()),
670 array_def.n.style(colors::primitive()),
671 "]".style(colors::punctuation())
672 )?;
673 }
674 Def::Map(map_def) => {
675 let map_name = if shape.type_identifier.contains("BTreeMap") {
676 "BTreeMap"
677 } else {
678 "HashMap"
679 };
680 write!(output, "{}", map_name.style(colors::container()))?;
681 write!(output, "{}", "<".style(colors::punctuation()))?;
682 write_type_name_colored(map_def.k, output)?;
683 write!(output, "{} ", ",".style(colors::punctuation()))?;
684 write_type_name_colored(map_def.v, output)?;
685 write!(output, "{}", ">".style(colors::punctuation()))?;
686 }
687 Def::Option(option_def) => {
688 write!(output, "{}", "Option".style(colors::container()))?;
689 write!(output, "{}", "<".style(colors::punctuation()))?;
690 write_type_name_colored(option_def.t, output)?;
691 write!(output, "{}", ">".style(colors::punctuation()))?;
692 }
693 _ => {
694 let id = shape.type_identifier;
695 if is_primitive_type(id) {
696 write!(output, "{}", id.style(colors::primitive()))?;
697 } else {
698 write!(output, "{}", id.style(colors::type_name()))?;
699 }
700 }
701 }
702 Ok(())
703}
704
705fn is_primitive_type(id: &str) -> bool {
707 matches!(
708 id,
709 "u8" | "u16"
710 | "u32"
711 | "u64"
712 | "u128"
713 | "usize"
714 | "i8"
715 | "i16"
716 | "i32"
717 | "i64"
718 | "i128"
719 | "isize"
720 | "f32"
721 | "f64"
722 | "bool"
723 | "char"
724 | "str"
725 | "&str"
726 | "String"
727 )
728}
729
730struct SpanTrackingContext<'a> {
732 output: String,
733 spans: BTreeMap<Path, FieldSpan>,
734 type_name_span: Option<Span>,
736 current_type: Option<&'static str>,
738 config: &'a ShapeFormatConfig,
740}
741
742impl<'a> SpanTrackingContext<'a> {
743 const fn new(config: &'a ShapeFormatConfig) -> Self {
744 Self {
745 output: String::new(),
746 spans: BTreeMap::new(),
747 type_name_span: None,
748 current_type: None,
749 config,
750 }
751 }
752
753 const fn len(&self) -> usize {
754 self.output.len()
755 }
756
757 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
758 self.spans.insert(
759 path,
760 FieldSpan {
761 key: key_span,
762 value: value_span,
763 },
764 );
765 }
766}
767
768fn format_shape_into_with_spans(shape: &Shape, ctx: &mut SpanTrackingContext) -> core::fmt::Result {
770 let mut printed: BTreeSet<&'static str> = BTreeSet::new();
772 let mut queue: Vec<&Shape> = Vec::new();
774
775 queue.push(shape);
777
778 while let Some(current) = queue.pop() {
779 if !printed.insert(current.type_identifier) {
781 continue;
782 }
783
784 if printed.len() > 1 {
786 writeln!(ctx.output)?;
787 writeln!(ctx.output)?;
788 }
789
790 match current.def {
793 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
794 printed.remove(current.type_identifier);
796 continue;
797 }
798 _ => {}
799 }
800
801 match ¤t.ty {
803 Type::User(user_type) => match user_type {
804 UserType::Struct(struct_type) => {
805 ctx.current_type = Some(current.type_identifier);
806 format_struct_with_spans(current, struct_type, ctx)?;
807 ctx.current_type = None;
808 if ctx.config.expand_nested_types {
810 collect_nested_types(struct_type, &mut queue);
811 }
812 }
813 UserType::Enum(enum_type) => {
814 ctx.current_type = Some(current.type_identifier);
815 format_enum_with_spans(current, enum_type, ctx)?;
816 ctx.current_type = None;
817 if ctx.config.expand_nested_types {
819 for variant in enum_type.variants {
820 collect_nested_types(&variant.data, &mut queue);
821 }
822 }
823 }
824 UserType::Union(_) | UserType::Opaque => {
825 printed.remove(current.type_identifier);
828 }
829 },
830 _ => {
831 printed.remove(current.type_identifier);
833 }
834 }
835 }
836 Ok(())
837}
838
839fn format_struct_with_spans(
840 shape: &Shape,
841 struct_type: &StructType,
842 ctx: &mut SpanTrackingContext,
843) -> core::fmt::Result {
844 let type_start = ctx.len();
846
847 if ctx.config.show_doc_comments {
849 write_doc_comments(shape.doc, &mut ctx.output, "")?;
850 }
851
852 writeln!(ctx.output, "#[derive(Facet)]")?;
854
855 write_facet_attrs(shape, &mut ctx.output)?;
857
858 if ctx.config.show_third_party_attrs {
860 write_third_party_attrs(shape.attributes, &mut ctx.output, "")?;
861 }
862
863 match struct_type.kind {
865 StructKind::Struct => {
866 write!(ctx.output, "struct ")?;
867 let type_name_start = ctx.len();
868 write!(ctx.output, "{}", shape.type_identifier)?;
869 let type_name_end = ctx.len();
870 ctx.type_name_span = Some((type_name_start, type_name_end));
871 writeln!(ctx.output, " {{")?;
872 for field in struct_type.fields {
873 if ctx.config.show_doc_comments {
875 write_doc_comments(field.doc, &mut ctx.output, " ")?;
876 }
877 if ctx.config.show_third_party_attrs {
879 write_field_third_party_attrs(field, &mut ctx.output, " ")?;
880 }
881 write!(ctx.output, " ")?;
882 let key_start = ctx.len();
884 write!(ctx.output, "{}", field.name)?;
885 let key_end = ctx.len();
886 write!(ctx.output, ": ")?;
887 let value_start = ctx.len();
889 write_type_name(field.shape(), &mut ctx.output)?;
890 let value_end = ctx.len();
891 ctx.record_field_span(
892 vec![PathSegment::Field(Cow::Borrowed(field.name))],
893 (key_start, key_end),
894 (value_start, value_end),
895 );
896 writeln!(ctx.output, ",")?;
897 }
898 write!(ctx.output, "}}")?;
899 }
900 StructKind::Tuple | StructKind::TupleStruct => {
901 write!(ctx.output, "struct ")?;
902 let type_name_start = ctx.len();
903 write!(ctx.output, "{}", shape.type_identifier)?;
904 let type_name_end = ctx.len();
905 ctx.type_name_span = Some((type_name_start, type_name_end));
906 write!(ctx.output, "(")?;
907 for (i, field) in struct_type.fields.iter().enumerate() {
908 if i > 0 {
909 write!(ctx.output, ", ")?;
910 }
911 let type_start = ctx.len();
913 write_type_name(field.shape(), &mut ctx.output)?;
914 let type_end = ctx.len();
915 let field_name = if !field.name.is_empty() {
917 field.name
918 } else {
919 continue;
921 };
922 ctx.record_field_span(
923 vec![PathSegment::Field(Cow::Borrowed(field_name))],
924 (type_start, type_end), (type_start, type_end),
926 );
927 }
928 write!(ctx.output, ");")?;
929 }
930 StructKind::Unit => {
931 write!(ctx.output, "struct ")?;
932 let type_name_start = ctx.len();
933 write!(ctx.output, "{}", shape.type_identifier)?;
934 let type_name_end = ctx.len();
935 ctx.type_name_span = Some((type_name_start, type_name_end));
936 write!(ctx.output, ";")?;
937 }
938 }
939
940 let type_end = ctx.len();
942 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
943
944 Ok(())
945}
946
947fn format_enum_with_spans(
948 shape: &Shape,
949 enum_type: &EnumType,
950 ctx: &mut SpanTrackingContext,
951) -> core::fmt::Result {
952 let type_start = ctx.len();
954
955 if ctx.config.show_doc_comments {
957 write_doc_comments(shape.doc, &mut ctx.output, "")?;
958 }
959
960 writeln!(ctx.output, "#[derive(Facet)]")?;
962
963 let repr_str = match enum_type.enum_repr {
965 EnumRepr::RustNPO => None,
966 EnumRepr::U8 => Some("u8"),
967 EnumRepr::U16 => Some("u16"),
968 EnumRepr::U32 => Some("u32"),
969 EnumRepr::U64 => Some("u64"),
970 EnumRepr::USize => Some("usize"),
971 EnumRepr::I8 => Some("i8"),
972 EnumRepr::I16 => Some("i16"),
973 EnumRepr::I32 => Some("i32"),
974 EnumRepr::I64 => Some("i64"),
975 EnumRepr::ISize => Some("isize"),
976 };
977
978 if let Some(repr) = repr_str {
979 writeln!(ctx.output, "#[repr({repr})]")?;
980 }
981
982 write_facet_attrs(shape, &mut ctx.output)?;
984
985 if ctx.config.show_third_party_attrs {
987 write_third_party_attrs(shape.attributes, &mut ctx.output, "")?;
988 }
989
990 write!(ctx.output, "enum ")?;
992 let type_name_start = ctx.len();
993 write!(ctx.output, "{}", shape.type_identifier)?;
994 let type_name_end = ctx.len();
995 ctx.type_name_span = Some((type_name_start, type_name_end));
996 writeln!(ctx.output, " {{")?;
997
998 for variant in enum_type.variants {
999 if ctx.config.show_doc_comments {
1001 write_doc_comments(variant.doc, &mut ctx.output, " ")?;
1002 }
1003 if ctx.config.show_third_party_attrs {
1005 write_variant_third_party_attrs(variant, &mut ctx.output, " ")?;
1006 }
1007
1008 match variant.data.kind {
1009 StructKind::Unit => {
1010 write!(ctx.output, " ")?;
1011 let name_start = ctx.len();
1013 write!(ctx.output, "{}", variant.name)?;
1014 let name_end = ctx.len();
1015 ctx.record_field_span(
1016 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1017 (name_start, name_end),
1018 (name_start, name_end),
1019 );
1020 writeln!(ctx.output, ",")?;
1021 }
1022 StructKind::Tuple | StructKind::TupleStruct => {
1023 write!(ctx.output, " ")?;
1024 let variant_name_start = ctx.len();
1025 write!(ctx.output, "{}", variant.name)?;
1026 let variant_name_end = ctx.len();
1027 write!(ctx.output, "(")?;
1028 let tuple_start = ctx.len();
1029 for (i, field) in variant.data.fields.iter().enumerate() {
1030 if i > 0 {
1031 write!(ctx.output, ", ")?;
1032 }
1033 let type_start = ctx.len();
1034 write_type_name(field.shape(), &mut ctx.output)?;
1035 let type_end = ctx.len();
1036 if !field.name.is_empty() {
1038 ctx.record_field_span(
1039 vec![
1040 PathSegment::Variant(Cow::Borrowed(variant.name)),
1041 PathSegment::Field(Cow::Borrowed(field.name)),
1042 ],
1043 (type_start, type_end),
1044 (type_start, type_end),
1045 );
1046 }
1047 }
1048 write!(ctx.output, ")")?;
1049 let tuple_end = ctx.len();
1050 ctx.record_field_span(
1052 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1053 (variant_name_start, variant_name_end),
1054 (tuple_start, tuple_end),
1055 );
1056 writeln!(ctx.output, ",")?;
1057 }
1058 StructKind::Struct => {
1059 write!(ctx.output, " ")?;
1060 let variant_name_start = ctx.len();
1061 write!(ctx.output, "{}", variant.name)?;
1062 let variant_name_end = ctx.len();
1063 writeln!(ctx.output, " {{")?;
1064 let struct_start = ctx.len();
1065 for field in variant.data.fields {
1066 if ctx.config.show_doc_comments {
1068 write_doc_comments(field.doc, &mut ctx.output, " ")?;
1069 }
1070 if ctx.config.show_third_party_attrs {
1072 write_field_third_party_attrs(field, &mut ctx.output, " ")?;
1073 }
1074 write!(ctx.output, " ")?;
1075 let key_start = ctx.len();
1076 write!(ctx.output, "{}", field.name)?;
1077 let key_end = ctx.len();
1078 write!(ctx.output, ": ")?;
1079 let value_start = ctx.len();
1080 write_type_name(field.shape(), &mut ctx.output)?;
1081 let value_end = ctx.len();
1082 ctx.record_field_span(
1083 vec![
1084 PathSegment::Variant(Cow::Borrowed(variant.name)),
1085 PathSegment::Field(Cow::Borrowed(field.name)),
1086 ],
1087 (key_start, key_end),
1088 (value_start, value_end),
1089 );
1090 writeln!(ctx.output, ",")?;
1091 }
1092 write!(ctx.output, " }}")?;
1093 let struct_end = ctx.len();
1094 ctx.record_field_span(
1096 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1097 (variant_name_start, variant_name_end),
1098 (struct_start, struct_end),
1099 );
1100 writeln!(ctx.output, ",")?;
1101 }
1102 }
1103 }
1104
1105 write!(ctx.output, "}}")?;
1106
1107 let type_end = ctx.len();
1109 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
1110
1111 Ok(())
1112}
1113
1114fn collect_nested_types<'a>(struct_type: &'a StructType, queue: &mut Vec<&'a Shape>) {
1116 for field in struct_type.fields {
1117 collect_from_shape(field.shape(), queue);
1118 }
1119}
1120
1121fn collect_from_shape<'a>(shape: &'a Shape, queue: &mut Vec<&'a Shape>) {
1123 match shape.def {
1124 Def::List(list_def) => collect_from_shape(list_def.t, queue),
1125 Def::Array(array_def) => collect_from_shape(array_def.t, queue),
1126 Def::Map(map_def) => {
1127 collect_from_shape(map_def.k, queue);
1128 collect_from_shape(map_def.v, queue);
1129 }
1130 Def::Option(option_def) => collect_from_shape(option_def.t, queue),
1131 _ => {
1132 if let Type::User(UserType::Struct(_) | UserType::Enum(_)) = &shape.ty {
1134 queue.push(shape);
1135 }
1136 }
1137 }
1138}
1139
1140fn write_facet_attrs(shape: &Shape, output: &mut String) -> core::fmt::Result {
1142 let mut attrs: Vec<String> = Vec::new();
1143
1144 if let Some(tag) = shape.get_tag_attr() {
1145 if let Some(content) = shape.get_content_attr() {
1146 attrs.push(alloc::format!("tag = \"{tag}\", content = \"{content}\""));
1147 } else {
1148 attrs.push(alloc::format!("tag = \"{tag}\""));
1149 }
1150 }
1151
1152 if shape.is_untagged() {
1153 attrs.push("untagged".into());
1154 }
1155
1156 if shape.has_deny_unknown_fields_attr() {
1157 attrs.push("deny_unknown_fields".into());
1158 }
1159
1160 if !attrs.is_empty() {
1161 writeln!(output, "#[facet({})]", attrs.join(", "))?;
1162 }
1163
1164 Ok(())
1165}
1166
1167fn write_doc_comments(doc: &[&str], output: &mut String, indent: &str) -> core::fmt::Result {
1169 for line in doc {
1170 write!(output, "{indent}")?;
1171 writeln!(output, "///{line}")?;
1172 }
1173 Ok(())
1174}
1175
1176fn write_third_party_attrs(
1178 attributes: &[Attr],
1179 output: &mut String,
1180 indent: &str,
1181) -> core::fmt::Result {
1182 let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
1184 for attr in attributes {
1185 if let Some(ns) = attr.ns {
1186 by_namespace.entry(ns).or_default().push(attr.key);
1187 }
1188 }
1189
1190 for (ns, keys) in by_namespace {
1192 write!(output, "{indent}")?;
1193 write!(output, "#[facet(")?;
1194 for (i, key) in keys.iter().enumerate() {
1195 if i > 0 {
1196 write!(output, ", ")?;
1197 }
1198 write!(output, "{ns}::{key}")?;
1199 }
1200 writeln!(output, ")]")?;
1201 }
1202 Ok(())
1203}
1204
1205fn write_field_third_party_attrs(
1207 field: &Field,
1208 output: &mut String,
1209 indent: &str,
1210) -> core::fmt::Result {
1211 write_third_party_attrs(field.attributes, output, indent)
1212}
1213
1214fn write_variant_third_party_attrs(
1216 variant: &Variant,
1217 output: &mut String,
1218 indent: &str,
1219) -> core::fmt::Result {
1220 write_third_party_attrs(variant.attributes, output, indent)
1221}
1222
1223fn write_type_name(shape: &Shape, output: &mut String) -> core::fmt::Result {
1224 match shape.def {
1225 Def::Scalar => {
1226 write!(output, "{}", shape.type_identifier)?;
1227 }
1228 Def::Pointer(_) => {
1229 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
1230 && let Def::Array(array_def) = r.target.def
1231 {
1232 write!(output, "&[")?;
1233 write_type_name(array_def.t, output)?;
1234 write!(output, "; {}]", array_def.n)?;
1235 return Ok(());
1236 }
1237 write!(output, "{}", shape.type_identifier)?;
1238 }
1239 Def::List(list_def) => {
1240 write!(output, "Vec<")?;
1241 write_type_name(list_def.t, output)?;
1242 write!(output, ">")?;
1243 }
1244 Def::Array(array_def) => {
1245 write!(output, "[")?;
1246 write_type_name(array_def.t, output)?;
1247 write!(output, "; {}]", array_def.n)?;
1248 }
1249 Def::Map(map_def) => {
1250 let map_name = if shape.type_identifier.contains("BTreeMap") {
1251 "BTreeMap"
1252 } else {
1253 "HashMap"
1254 };
1255 write!(output, "{map_name}<")?;
1256 write_type_name(map_def.k, output)?;
1257 write!(output, ", ")?;
1258 write_type_name(map_def.v, output)?;
1259 write!(output, ">")?;
1260 }
1261 Def::Option(option_def) => {
1262 write!(output, "Option<")?;
1263 write_type_name(option_def.t, output)?;
1264 write!(output, ">")?;
1265 }
1266 _ => {
1267 write!(output, "{}", shape.type_identifier)?;
1268 }
1269 }
1270 Ok(())
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275 use super::*;
1276 use facet::Facet;
1277
1278 #[test]
1279 fn test_simple_struct() {
1280 #[derive(Facet)]
1281 struct Simple {
1282 name: String,
1283 count: u32,
1284 }
1285
1286 let output = format_shape(Simple::SHAPE);
1287 assert!(output.contains("struct Simple"));
1288 assert!(output.contains("name: String"));
1289 assert!(output.contains("count: u32"));
1290 }
1291
1292 #[test]
1293 fn test_enum_with_tag() {
1294 #[derive(Facet)]
1295 #[repr(C)]
1296 #[facet(tag = "type")]
1297 #[allow(dead_code)]
1298 enum Tagged {
1299 A { x: i32 },
1300 B { y: String },
1301 }
1302
1303 let output = format_shape(Tagged::SHAPE);
1304 assert!(output.contains("enum Tagged"));
1305 assert!(output.contains("#[facet(tag = \"type\")]"));
1306 }
1307
1308 #[test]
1309 fn test_nested_types() {
1310 #[derive(Facet)]
1311 #[allow(dead_code)]
1312 struct Inner {
1313 value: i32,
1314 }
1315
1316 #[derive(Facet)]
1317 #[allow(dead_code)]
1318 struct Outer {
1319 inner: Inner,
1320 name: String,
1321 }
1322
1323 let output = format_shape(Outer::SHAPE);
1324 assert!(output.contains("struct Outer"), "Missing Outer: {output}");
1326 assert!(
1327 output.contains("inner: Inner"),
1328 "Missing inner field: {output}"
1329 );
1330 assert!(
1331 output.contains("struct Inner"),
1332 "Missing Inner definition: {output}"
1333 );
1334 assert!(
1335 output.contains("value: i32"),
1336 "Missing value field: {output}"
1337 );
1338 }
1339
1340 #[test]
1341 fn test_nested_in_vec() {
1342 #[derive(Facet)]
1343 #[allow(dead_code)]
1344 struct Item {
1345 id: u32,
1346 }
1347
1348 #[derive(Facet)]
1349 #[allow(dead_code)]
1350 struct Container {
1351 items: Vec<Item>,
1352 }
1353
1354 let output = format_shape(Container::SHAPE);
1355 assert!(
1357 output.contains("struct Container"),
1358 "Missing Container: {output}"
1359 );
1360 assert!(
1361 output.contains("items: Vec<Item>"),
1362 "Missing items field: {output}"
1363 );
1364 assert!(
1365 output.contains("struct Item"),
1366 "Missing Item definition: {output}"
1367 );
1368 }
1369
1370 #[test]
1371 fn test_format_shape_with_spans() {
1372 #[derive(Facet)]
1373 #[allow(dead_code)]
1374 struct Config {
1375 name: String,
1376 max_retries: u8,
1377 enabled: bool,
1378 }
1379
1380 let result = format_shape_with_spans(Config::SHAPE);
1381
1382 let name_path = vec![PathSegment::Field(Cow::Borrowed("name"))];
1384 let retries_path = vec![PathSegment::Field(Cow::Borrowed("max_retries"))];
1385 let enabled_path = vec![PathSegment::Field(Cow::Borrowed("enabled"))];
1386
1387 assert!(
1388 result.spans.contains_key(&name_path),
1389 "Missing span for 'name' field. Spans: {:?}",
1390 result.spans
1391 );
1392 assert!(
1393 result.spans.contains_key(&retries_path),
1394 "Missing span for 'max_retries' field. Spans: {:?}",
1395 result.spans
1396 );
1397 assert!(
1398 result.spans.contains_key(&enabled_path),
1399 "Missing span for 'enabled' field. Spans: {:?}",
1400 result.spans
1401 );
1402
1403 let field_span = &result.spans[&retries_path];
1405 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1406 assert_eq!(spanned_text, "u8", "Expected 'u8', got '{spanned_text}'");
1407 }
1408
1409 #[test]
1410 fn test_format_enum_with_spans() {
1411 #[derive(Facet)]
1412 #[repr(u8)]
1413 #[allow(dead_code)]
1414 enum Status {
1415 Active,
1416 Pending,
1417 Error { code: i32, message: String },
1418 }
1419
1420 let result = format_shape_with_spans(Status::SHAPE);
1421
1422 let active_path = vec![PathSegment::Variant(Cow::Borrowed("Active"))];
1424 let error_path = vec![PathSegment::Variant(Cow::Borrowed("Error"))];
1425 let error_code_path = vec![
1426 PathSegment::Variant(Cow::Borrowed("Error")),
1427 PathSegment::Field(Cow::Borrowed("code")),
1428 ];
1429
1430 assert!(
1431 result.spans.contains_key(&active_path),
1432 "Missing span for 'Active' variant. Spans: {:?}",
1433 result.spans
1434 );
1435 assert!(
1436 result.spans.contains_key(&error_path),
1437 "Missing span for 'Error' variant. Spans: {:?}",
1438 result.spans
1439 );
1440 assert!(
1441 result.spans.contains_key(&error_code_path),
1442 "Missing span for 'Error.code' field. Spans: {:?}",
1443 result.spans
1444 );
1445
1446 let field_span = &result.spans[&error_code_path];
1448 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1449 assert_eq!(spanned_text, "i32", "Expected 'i32', got '{spanned_text}'");
1450 }
1451
1452 #[test]
1453 fn test_format_with_doc_comments() {
1454 #[derive(Facet)]
1456 #[allow(dead_code)]
1457 struct Config {
1458 name: String,
1460 max_retries: u8,
1462 }
1463
1464 let output = format_shape(Config::SHAPE);
1466 assert!(
1467 output.contains("/// A configuration struct"),
1468 "Should contain struct doc comment: {output}"
1469 );
1470 assert!(
1471 output.contains("/// The name of the configuration"),
1472 "Should contain field doc comment: {output}"
1473 );
1474 assert!(
1475 output.contains("/// Maximum number of retries"),
1476 "Should contain field doc comment: {output}"
1477 );
1478
1479 let config = ShapeFormatConfig::new();
1481 let output_without = format_shape_with_config(Config::SHAPE, &config);
1482 assert!(
1483 !output_without.contains("///"),
1484 "Should not contain doc comments when disabled: {output_without}"
1485 );
1486 }
1487
1488 #[test]
1489 fn test_format_enum_with_doc_comments() {
1490 #[derive(Facet)]
1492 #[repr(u8)]
1493 #[allow(dead_code)]
1494 enum Status {
1495 Active,
1497 Error {
1499 code: i32,
1501 },
1502 }
1503
1504 let config = ShapeFormatConfig::new().with_doc_comments();
1505 let output = format_shape_with_config(Status::SHAPE, &config);
1506
1507 assert!(
1508 output.contains("/// Status of an operation"),
1509 "Should contain enum doc comment: {output}"
1510 );
1511 assert!(
1512 output.contains("/// The operation is active"),
1513 "Should contain variant doc comment: {output}"
1514 );
1515 assert!(
1516 output.contains("/// The operation failed"),
1517 "Should contain variant doc comment: {output}"
1518 );
1519 assert!(
1520 output.contains("/// Error code"),
1521 "Should contain variant field doc comment: {output}"
1522 );
1523 }
1524}