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, ConstTypeId, Def, EnumRepr, EnumType, Field, PointerType, Shape, StructKind, StructType,
16 Type, 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 pub type_span: Option<Span>,
160 pub type_end_span: Option<Span>,
162}
163
164pub fn strip_ansi(s: &str) -> String {
166 let mut result = String::with_capacity(s.len());
167 let mut chars = s.chars().peekable();
168
169 while let Some(c) = chars.next() {
170 if c == '\x1b' {
171 if chars.peek() == Some(&'[') {
173 chars.next(); while let Some(&next) = chars.peek() {
176 chars.next();
177 if next.is_ascii_alphabetic() {
178 break;
179 }
180 }
181 }
182 } else {
183 result.push(c);
184 }
185 }
186 result
187}
188
189pub fn format_shape(shape: &Shape) -> String {
193 strip_ansi(&format_shape_colored(shape))
194}
195
196pub fn format_shape_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
198 strip_ansi(&format_shape_colored_with_config(shape, config))
199}
200
201pub fn format_shape_with_spans(shape: &Shape) -> FormattedShape {
206 format_shape_with_spans_and_config(shape, &ShapeFormatConfig::default().with_all_metadata())
207}
208
209pub fn format_shape_with_spans_and_config(
212 shape: &Shape,
213 config: &ShapeFormatConfig,
214) -> FormattedShape {
215 let mut ctx = SpanTrackingContext::new(config);
216 format_shape_into_with_spans(shape, &mut ctx).expect("Formatting failed");
217 FormattedShape {
218 text: ctx.output,
219 spans: ctx.spans,
220 type_name_span: ctx.type_name_span,
221 type_span: ctx.type_span,
222 type_end_span: ctx.type_end_span,
223 }
224}
225
226pub fn format_shape_colored(shape: &Shape) -> String {
230 format_shape_colored_with_config(shape, &ShapeFormatConfig::default().with_all_metadata())
231}
232
233pub fn format_shape_colored_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
235 let mut output = String::new();
236 format_shape_colored_into_with_config(shape, &mut output, config).expect("Formatting failed");
237 output
238}
239
240pub fn format_shape_colored_into(shape: &Shape, output: &mut String) -> core::fmt::Result {
242 format_shape_colored_into_with_config(shape, output, &ShapeFormatConfig::default())
243}
244
245pub fn format_shape_colored_into_with_config(
247 shape: &Shape,
248 output: &mut String,
249 config: &ShapeFormatConfig,
250) -> core::fmt::Result {
251 let mut printed: BTreeSet<ConstTypeId> = BTreeSet::new();
252 let mut queue: Vec<&Shape> = Vec::new();
253 queue.push(shape);
254
255 while let Some(current) = queue.pop() {
256 if !printed.insert(current.id) {
257 continue;
258 }
259
260 if printed.len() > 1 {
261 writeln!(output)?;
262 writeln!(output)?;
263 }
264
265 match current.def {
266 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
267 printed.remove(¤t.id);
268 continue;
269 }
270 _ => {}
271 }
272
273 match ¤t.ty {
274 Type::User(user_type) => match user_type {
275 UserType::Struct(struct_type) => {
276 format_struct_colored(current, struct_type, output, config)?;
277 collect_nested_types(struct_type, &mut queue);
278 }
279 UserType::Enum(enum_type) => {
280 format_enum_colored(current, enum_type, output, config)?;
281 for variant in enum_type.variants {
282 collect_nested_types(&variant.data, &mut queue);
283 }
284 }
285 UserType::Union(_) | UserType::Opaque => {
286 printed.remove(¤t.id);
287 }
288 },
289 _ => {
290 printed.remove(¤t.id);
291 }
292 }
293 }
294 Ok(())
295}
296
297fn format_struct_colored(
298 shape: &Shape,
299 struct_type: &StructType,
300 output: &mut String,
301 config: &ShapeFormatConfig,
302) -> core::fmt::Result {
303 if config.show_doc_comments {
305 write_doc_comments_colored(shape.doc, output, "")?;
306 }
307
308 write!(output, "{}", "#[".style(colors::attribute()))?;
310 write!(output, "{}", "derive".style(colors::attribute_content()))?;
311 write!(output, "{}", "(".style(colors::attribute()))?;
312 write!(output, "{}", "Facet".style(colors::attribute_content()))?;
313 writeln!(output, "{}", ")]".style(colors::attribute()))?;
314
315 write_facet_attrs_colored(shape, output)?;
317
318 if config.show_third_party_attrs {
320 write_third_party_attrs_colored(shape.attributes, output, "")?;
321 }
322
323 match struct_type.kind {
324 StructKind::Struct => {
325 write!(output, "{} ", "struct".style(colors::keyword()))?;
326 write!(
327 output,
328 "{}",
329 shape.type_identifier.style(colors::type_name())
330 )?;
331 writeln!(output, " {}", "{".style(colors::punctuation()))?;
332
333 for (i, field) in struct_type.fields.iter().enumerate() {
334 if i > 0 {
336 writeln!(output)?;
337 }
338 if config.show_doc_comments {
340 write_doc_comments_colored(field.doc, output, " ")?;
341 }
342 if config.show_third_party_attrs {
344 write_field_third_party_attrs_colored(field, output, " ")?;
345 }
346 write!(output, " {}", field.name.style(colors::field_name()))?;
347 write!(output, "{} ", ":".style(colors::punctuation()))?;
348 write_type_name_colored(field.shape(), output)?;
349 writeln!(output, "{}", ",".style(colors::punctuation()))?;
350 }
351 write!(output, "{}", "}".style(colors::punctuation()))?;
352 }
353 StructKind::Tuple | StructKind::TupleStruct => {
354 write!(output, "{} ", "struct".style(colors::keyword()))?;
355 write!(
356 output,
357 "{}",
358 shape.type_identifier.style(colors::type_name())
359 )?;
360 write!(output, "{}", "(".style(colors::punctuation()))?;
361 for (i, field) in struct_type.fields.iter().enumerate() {
362 if i > 0 {
363 write!(output, "{} ", ",".style(colors::punctuation()))?;
364 }
365 write_type_name_colored(field.shape(), output)?;
366 }
367 write!(
368 output,
369 "{}{}",
370 ")".style(colors::punctuation()),
371 ";".style(colors::punctuation())
372 )?;
373 }
374 StructKind::Unit => {
375 write!(output, "{} ", "struct".style(colors::keyword()))?;
376 write!(
377 output,
378 "{}",
379 shape.type_identifier.style(colors::type_name())
380 )?;
381 write!(output, "{}", ";".style(colors::punctuation()))?;
382 }
383 }
384 Ok(())
385}
386
387fn format_enum_colored(
388 shape: &Shape,
389 enum_type: &EnumType,
390 output: &mut String,
391 config: &ShapeFormatConfig,
392) -> core::fmt::Result {
393 if config.show_doc_comments {
395 write_doc_comments_colored(shape.doc, output, "")?;
396 }
397
398 write!(output, "{}", "#[".style(colors::attribute()))?;
400 write!(output, "{}", "derive".style(colors::attribute_content()))?;
401 write!(output, "{}", "(".style(colors::attribute()))?;
402 write!(output, "{}", "Facet".style(colors::attribute_content()))?;
403 writeln!(output, "{}", ")]".style(colors::attribute()))?;
404
405 let repr_str = match enum_type.enum_repr {
407 EnumRepr::Rust => None,
408 EnumRepr::RustNPO => None,
409 EnumRepr::U8 => Some("u8"),
410 EnumRepr::U16 => Some("u16"),
411 EnumRepr::U32 => Some("u32"),
412 EnumRepr::U64 => Some("u64"),
413 EnumRepr::USize => Some("usize"),
414 EnumRepr::I8 => Some("i8"),
415 EnumRepr::I16 => Some("i16"),
416 EnumRepr::I32 => Some("i32"),
417 EnumRepr::I64 => Some("i64"),
418 EnumRepr::ISize => Some("isize"),
419 };
420
421 if let Some(repr) = repr_str {
422 write!(output, "{}", "#[".style(colors::attribute()))?;
423 write!(output, "{}", "repr".style(colors::attribute_content()))?;
424 write!(output, "{}", "(".style(colors::attribute()))?;
425 write!(output, "{}", repr.style(colors::primitive()))?;
426 writeln!(output, "{}", ")]".style(colors::attribute()))?;
427 }
428
429 write_facet_attrs_colored(shape, output)?;
431
432 if config.show_third_party_attrs {
434 write_third_party_attrs_colored(shape.attributes, output, "")?;
435 }
436
437 write!(output, "{} ", "enum".style(colors::keyword()))?;
439 write!(
440 output,
441 "{}",
442 shape.type_identifier.style(colors::type_name())
443 )?;
444 writeln!(output, " {}", "{".style(colors::punctuation()))?;
445
446 for (vi, variant) in enum_type.variants.iter().enumerate() {
447 if vi > 0 {
449 writeln!(output)?;
450 }
451 if config.show_doc_comments {
453 write_doc_comments_colored(variant.doc, output, " ")?;
454 }
455 if config.show_third_party_attrs {
457 write_variant_third_party_attrs_colored(variant, output, " ")?;
458 }
459
460 match variant.data.kind {
461 StructKind::Unit => {
462 write!(output, " {}", variant.name.style(colors::type_name()))?;
463 writeln!(output, "{}", ",".style(colors::punctuation()))?;
464 }
465 StructKind::Tuple | StructKind::TupleStruct => {
466 write!(output, " {}", variant.name.style(colors::type_name()))?;
467 write!(output, "{}", "(".style(colors::punctuation()))?;
468 for (i, field) in variant.data.fields.iter().enumerate() {
469 if i > 0 {
470 write!(output, "{} ", ",".style(colors::punctuation()))?;
471 }
472 write_type_name_colored(field.shape(), output)?;
473 }
474 write!(output, "{}", ")".style(colors::punctuation()))?;
475 writeln!(output, "{}", ",".style(colors::punctuation()))?;
476 }
477 StructKind::Struct => {
478 write!(output, " {}", variant.name.style(colors::type_name()))?;
479 writeln!(output, " {}", "{".style(colors::punctuation()))?;
480 for (fi, field) in variant.data.fields.iter().enumerate() {
481 if fi > 0 {
483 writeln!(output)?;
484 }
485 if config.show_doc_comments {
487 write_doc_comments_colored(field.doc, output, " ")?;
488 }
489 if config.show_third_party_attrs {
491 write_field_third_party_attrs_colored(field, output, " ")?;
492 }
493 write!(output, " {}", field.name.style(colors::field_name()))?;
494 write!(output, "{} ", ":".style(colors::punctuation()))?;
495 write_type_name_colored(field.shape(), output)?;
496 writeln!(output, "{}", ",".style(colors::punctuation()))?;
497 }
498 write!(output, " {}", "}".style(colors::punctuation()))?;
499 writeln!(output, "{}", ",".style(colors::punctuation()))?;
500 }
501 }
502 }
503
504 write!(output, "{}", "}".style(colors::punctuation()))?;
505 Ok(())
506}
507
508fn write_facet_attrs_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
509 let mut attrs: Vec<String> = Vec::new();
510
511 if let Some(tag) = shape.get_tag_attr() {
512 if let Some(content) = shape.get_content_attr() {
513 attrs.push(alloc::format!(
514 "{}{}{}{}{}{}{}{}{}",
515 "tag".style(colors::attribute_content()),
516 " = ".style(colors::punctuation()),
517 "\"".style(colors::string()),
518 tag.style(colors::string()),
519 "\"".style(colors::string()),
520 ", ".style(colors::punctuation()),
521 "content".style(colors::attribute_content()),
522 " = ".style(colors::punctuation()),
523 format!("\"{content}\"").style(colors::string()),
524 ));
525 } else {
526 attrs.push(alloc::format!(
527 "{}{}{}",
528 "tag".style(colors::attribute_content()),
529 " = ".style(colors::punctuation()),
530 format!("\"{tag}\"").style(colors::string()),
531 ));
532 }
533 }
534
535 if shape.is_untagged() {
536 attrs.push(alloc::format!(
537 "{}",
538 "untagged".style(colors::attribute_content())
539 ));
540 }
541
542 if shape.has_deny_unknown_fields_attr() {
543 attrs.push(alloc::format!(
544 "{}",
545 "deny_unknown_fields".style(colors::attribute_content())
546 ));
547 }
548
549 if !attrs.is_empty() {
550 write!(output, "{}", "#[".style(colors::attribute()))?;
551 write!(output, "{}", "facet".style(colors::attribute_content()))?;
552 write!(output, "{}", "(".style(colors::attribute()))?;
553 write!(
554 output,
555 "{}",
556 attrs.join(&format!("{}", ", ".style(colors::punctuation())))
557 )?;
558 writeln!(output, "{}", ")]".style(colors::attribute()))?;
559 }
560
561 Ok(())
562}
563
564fn write_doc_comments_colored(
566 doc: &[&str],
567 output: &mut String,
568 indent: &str,
569) -> core::fmt::Result {
570 for line in doc {
571 write!(output, "{indent}")?;
572 writeln!(output, "{}", format!("///{line}").style(colors::comment()))?;
573 }
574 Ok(())
575}
576
577fn write_third_party_attrs_colored(
580 attributes: &[Attr],
581 output: &mut String,
582 indent: &str,
583) -> core::fmt::Result {
584 let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
586 for attr in attributes {
587 if let Some(ns) = attr.ns {
588 by_namespace.entry(ns).or_default().push(attr.key);
589 }
590 }
591
592 for (ns, keys) in by_namespace {
594 write!(output, "{indent}")?;
595 write!(output, "{}", "#[".style(colors::attribute()))?;
596 write!(output, "{}", "facet".style(colors::attribute_content()))?;
597 write!(output, "{}", "(".style(colors::attribute()))?;
598
599 for (i, key) in keys.iter().enumerate() {
600 if i > 0 {
601 write!(output, "{}", ", ".style(colors::punctuation()))?;
602 }
603 write!(output, "{}", ns.style(colors::attribute_content()))?;
604 write!(output, "{}", "::".style(colors::punctuation()))?;
605 write!(output, "{}", key.style(colors::attribute_content()))?;
606 }
607
608 write!(output, "{}", ")".style(colors::attribute()))?;
609 writeln!(output, "{}", "]".style(colors::attribute()))?;
610 }
611 Ok(())
612}
613
614fn write_field_third_party_attrs_colored(
616 field: &Field,
617 output: &mut String,
618 indent: &str,
619) -> core::fmt::Result {
620 write_third_party_attrs_colored(field.attributes, output, indent)
621}
622
623fn write_variant_third_party_attrs_colored(
625 variant: &Variant,
626 output: &mut String,
627 indent: &str,
628) -> core::fmt::Result {
629 write_third_party_attrs_colored(variant.attributes, output, indent)
630}
631
632fn write_type_name_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
633 match shape.def {
634 Def::Scalar => {
635 let id = shape.type_identifier;
637 if is_primitive_type(id) {
638 write!(output, "{}", id.style(colors::primitive()))?;
639 } else {
640 write!(output, "{}", id.style(colors::type_name()))?;
641 }
642 }
643 Def::Pointer(_) => {
644 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
645 && let Def::Array(array_def) = r.target.def
646 {
647 write!(output, "{}", "&[".style(colors::punctuation()))?;
648 write_type_name_colored(array_def.t, output)?;
649 write!(
650 output,
651 "{}{}{}",
652 "; ".style(colors::punctuation()),
653 array_def.n.style(colors::primitive()),
654 "]".style(colors::punctuation())
655 )?;
656 return Ok(());
657 }
658 write!(
659 output,
660 "{}",
661 shape.type_identifier.style(colors::type_name())
662 )?;
663 }
664 Def::List(list_def) => {
665 write!(output, "{}", "Vec".style(colors::container()))?;
666 write!(output, "{}", "<".style(colors::punctuation()))?;
667 write_type_name_colored(list_def.t, output)?;
668 write!(output, "{}", ">".style(colors::punctuation()))?;
669 }
670 Def::Array(array_def) => {
671 write!(output, "{}", "[".style(colors::punctuation()))?;
672 write_type_name_colored(array_def.t, output)?;
673 write!(
674 output,
675 "{}{}{}",
676 "; ".style(colors::punctuation()),
677 array_def.n.style(colors::primitive()),
678 "]".style(colors::punctuation())
679 )?;
680 }
681 Def::Map(map_def) => {
682 let map_name = if shape.type_identifier.contains("BTreeMap") {
683 "BTreeMap"
684 } else {
685 "HashMap"
686 };
687 write!(output, "{}", map_name.style(colors::container()))?;
688 write!(output, "{}", "<".style(colors::punctuation()))?;
689 write_type_name_colored(map_def.k, output)?;
690 write!(output, "{} ", ",".style(colors::punctuation()))?;
691 write_type_name_colored(map_def.v, output)?;
692 write!(output, "{}", ">".style(colors::punctuation()))?;
693 }
694 Def::Option(option_def) => {
695 write!(output, "{}", "Option".style(colors::container()))?;
696 write!(output, "{}", "<".style(colors::punctuation()))?;
697 write_type_name_colored(option_def.t, output)?;
698 write!(output, "{}", ">".style(colors::punctuation()))?;
699 }
700 _ => {
701 let id = shape.type_identifier;
702 if is_primitive_type(id) {
703 write!(output, "{}", id.style(colors::primitive()))?;
704 } else {
705 write!(output, "{}", id.style(colors::type_name()))?;
706 }
707 }
708 }
709 Ok(())
710}
711
712fn is_primitive_type(id: &str) -> bool {
714 matches!(
715 id,
716 "u8" | "u16"
717 | "u32"
718 | "u64"
719 | "u128"
720 | "usize"
721 | "i8"
722 | "i16"
723 | "i32"
724 | "i64"
725 | "i128"
726 | "isize"
727 | "f32"
728 | "f64"
729 | "bool"
730 | "char"
731 | "str"
732 | "&str"
733 | "String"
734 )
735}
736
737struct SpanTrackingContext<'a> {
739 output: String,
740 spans: BTreeMap<Path, FieldSpan>,
741 type_name_span: Option<Span>,
743 type_span: Option<Span>,
745 type_end_span: Option<Span>,
747 current_type: Option<&'static str>,
749 config: &'a ShapeFormatConfig,
751}
752
753impl<'a> SpanTrackingContext<'a> {
754 const fn new(config: &'a ShapeFormatConfig) -> Self {
755 Self {
756 output: String::new(),
757 spans: BTreeMap::new(),
758 type_name_span: None,
759 type_span: None,
760 type_end_span: None,
761 current_type: None,
762 config,
763 }
764 }
765
766 const fn len(&self) -> usize {
767 self.output.len()
768 }
769
770 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
771 self.spans.insert(
772 path,
773 FieldSpan {
774 key: key_span,
775 value: value_span,
776 },
777 );
778 }
779}
780
781fn format_shape_into_with_spans(shape: &Shape, ctx: &mut SpanTrackingContext) -> core::fmt::Result {
783 let mut printed: BTreeSet<ConstTypeId> = BTreeSet::new();
785 let mut queue: Vec<&Shape> = Vec::new();
787
788 queue.push(shape);
790
791 while let Some(current) = queue.pop() {
792 if !printed.insert(current.id) {
794 continue;
795 }
796
797 if printed.len() > 1 {
799 writeln!(ctx.output)?;
800 writeln!(ctx.output)?;
801 }
802
803 match current.def {
806 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
807 printed.remove(¤t.id);
809 continue;
810 }
811 _ => {}
812 }
813
814 match ¤t.ty {
816 Type::User(user_type) => match user_type {
817 UserType::Struct(struct_type) => {
818 ctx.current_type = Some(current.type_identifier);
819 format_struct_with_spans(current, struct_type, ctx)?;
820 ctx.current_type = None;
821 if ctx.config.expand_nested_types {
823 collect_nested_types(struct_type, &mut queue);
824 }
825 }
826 UserType::Enum(enum_type) => {
827 ctx.current_type = Some(current.type_identifier);
828 format_enum_with_spans(current, enum_type, ctx)?;
829 ctx.current_type = None;
830 if ctx.config.expand_nested_types {
832 for variant in enum_type.variants {
833 collect_nested_types(&variant.data, &mut queue);
834 }
835 }
836 }
837 UserType::Union(_) | UserType::Opaque => {
838 printed.remove(¤t.id);
841 }
842 },
843 _ => {
844 printed.remove(¤t.id);
846 }
847 }
848 }
849 Ok(())
850}
851
852fn format_struct_with_spans(
853 shape: &Shape,
854 struct_type: &StructType,
855 ctx: &mut SpanTrackingContext,
856) -> core::fmt::Result {
857 let type_start = ctx.len();
859
860 if ctx.config.show_doc_comments {
862 write_doc_comments(shape.doc, &mut ctx.output, "")?;
863 }
864
865 writeln!(ctx.output, "#[derive(Facet)]")?;
867
868 write_facet_attrs(shape, &mut ctx.output)?;
870
871 if ctx.config.show_third_party_attrs {
873 write_third_party_attrs(shape.attributes, &mut ctx.output, "")?;
874 }
875
876 match struct_type.kind {
878 StructKind::Struct => {
879 write!(ctx.output, "struct ")?;
880 let type_name_start = ctx.len();
881 write!(ctx.output, "{}", shape.type_identifier)?;
882 let type_name_end = ctx.len();
883 ctx.type_name_span = Some((type_name_start, type_name_end));
884 writeln!(ctx.output, " {{")?;
885 for field in struct_type.fields {
886 if ctx.config.show_doc_comments {
888 write_doc_comments(field.doc, &mut ctx.output, " ")?;
889 }
890 if ctx.config.show_third_party_attrs {
892 write_field_third_party_attrs(field, &mut ctx.output, " ")?;
893 }
894 write!(ctx.output, " ")?;
895 let key_start = ctx.len();
897 write!(ctx.output, "{}", field.name)?;
898 let key_end = ctx.len();
899 write!(ctx.output, ": ")?;
900 let value_start = ctx.len();
902 write_type_name(field.shape(), &mut ctx.output)?;
903 let value_end = ctx.len();
904 ctx.record_field_span(
905 vec![PathSegment::Field(Cow::Borrowed(field.name))],
906 (key_start, key_end),
907 (value_start, value_end),
908 );
909 writeln!(ctx.output, ",")?;
910 }
911 let type_end_start = ctx.len();
912 write!(ctx.output, "}}")?;
913 let type_end_end = ctx.len();
914 ctx.type_end_span = Some((type_end_start, type_end_end));
915 }
916 StructKind::Tuple | StructKind::TupleStruct => {
917 write!(ctx.output, "struct ")?;
918 let type_name_start = ctx.len();
919 write!(ctx.output, "{}", shape.type_identifier)?;
920 let type_name_end = ctx.len();
921 ctx.type_name_span = Some((type_name_start, type_name_end));
922 write!(ctx.output, "(")?;
923 for (i, field) in struct_type.fields.iter().enumerate() {
924 if i > 0 {
925 write!(ctx.output, ", ")?;
926 }
927 let type_start = ctx.len();
929 write_type_name(field.shape(), &mut ctx.output)?;
930 let type_end = ctx.len();
931 let field_name = if !field.name.is_empty() {
933 field.name
934 } else {
935 continue;
937 };
938 ctx.record_field_span(
939 vec![PathSegment::Field(Cow::Borrowed(field_name))],
940 (type_start, type_end), (type_start, type_end),
942 );
943 }
944 let type_end_start = ctx.len();
945 write!(ctx.output, ");")?;
946 let type_end_end = ctx.len();
947 ctx.type_end_span = Some((type_end_start, type_end_end));
948 }
949 StructKind::Unit => {
950 write!(ctx.output, "struct ")?;
951 let type_name_start = ctx.len();
952 write!(ctx.output, "{}", shape.type_identifier)?;
953 let type_name_end = ctx.len();
954 ctx.type_name_span = Some((type_name_start, type_name_end));
955 let type_end_start = ctx.len();
956 write!(ctx.output, ";")?;
957 let type_end_end = ctx.len();
958 ctx.type_end_span = Some((type_end_start, type_end_end));
959 }
960 }
961
962 let type_end = ctx.len();
964 ctx.type_span = Some((type_start, type_end));
965 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
966
967 Ok(())
968}
969
970fn format_enum_with_spans(
971 shape: &Shape,
972 enum_type: &EnumType,
973 ctx: &mut SpanTrackingContext,
974) -> core::fmt::Result {
975 let type_start = ctx.len();
977
978 if ctx.config.show_doc_comments {
980 write_doc_comments(shape.doc, &mut ctx.output, "")?;
981 }
982
983 writeln!(ctx.output, "#[derive(Facet)]")?;
985
986 let repr_str = match enum_type.enum_repr {
988 EnumRepr::Rust => None,
989 EnumRepr::RustNPO => None,
990 EnumRepr::U8 => Some("u8"),
991 EnumRepr::U16 => Some("u16"),
992 EnumRepr::U32 => Some("u32"),
993 EnumRepr::U64 => Some("u64"),
994 EnumRepr::USize => Some("usize"),
995 EnumRepr::I8 => Some("i8"),
996 EnumRepr::I16 => Some("i16"),
997 EnumRepr::I32 => Some("i32"),
998 EnumRepr::I64 => Some("i64"),
999 EnumRepr::ISize => Some("isize"),
1000 };
1001
1002 if let Some(repr) = repr_str {
1003 writeln!(ctx.output, "#[repr({repr})]")?;
1004 }
1005
1006 write_facet_attrs(shape, &mut ctx.output)?;
1008
1009 if ctx.config.show_third_party_attrs {
1011 write_third_party_attrs(shape.attributes, &mut ctx.output, "")?;
1012 }
1013
1014 write!(ctx.output, "enum ")?;
1016 let type_name_start = ctx.len();
1017 write!(ctx.output, "{}", shape.type_identifier)?;
1018 let type_name_end = ctx.len();
1019 ctx.type_name_span = Some((type_name_start, type_name_end));
1020 writeln!(ctx.output, " {{")?;
1021
1022 for variant in enum_type.variants {
1023 if ctx.config.show_doc_comments {
1025 write_doc_comments(variant.doc, &mut ctx.output, " ")?;
1026 }
1027 if ctx.config.show_third_party_attrs {
1029 write_variant_third_party_attrs(variant, &mut ctx.output, " ")?;
1030 }
1031
1032 match variant.data.kind {
1033 StructKind::Unit => {
1034 write!(ctx.output, " ")?;
1035 let name_start = ctx.len();
1037 write!(ctx.output, "{}", variant.name)?;
1038 let name_end = ctx.len();
1039 ctx.record_field_span(
1040 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1041 (name_start, name_end),
1042 (name_start, name_end),
1043 );
1044 writeln!(ctx.output, ",")?;
1045 }
1046 StructKind::Tuple | StructKind::TupleStruct => {
1047 write!(ctx.output, " ")?;
1048 let variant_name_start = ctx.len();
1049 write!(ctx.output, "{}", variant.name)?;
1050 let variant_name_end = ctx.len();
1051 write!(ctx.output, "(")?;
1052 let tuple_start = ctx.len();
1053 for (i, field) in variant.data.fields.iter().enumerate() {
1054 if i > 0 {
1055 write!(ctx.output, ", ")?;
1056 }
1057 let type_start = ctx.len();
1058 write_type_name(field.shape(), &mut ctx.output)?;
1059 let type_end = ctx.len();
1060 if !field.name.is_empty() {
1062 ctx.record_field_span(
1063 vec![
1064 PathSegment::Variant(Cow::Borrowed(variant.name)),
1065 PathSegment::Field(Cow::Borrowed(field.name)),
1066 ],
1067 (type_start, type_end),
1068 (type_start, type_end),
1069 );
1070 }
1071 }
1072 write!(ctx.output, ")")?;
1073 let tuple_end = ctx.len();
1074 ctx.record_field_span(
1076 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1077 (variant_name_start, variant_name_end),
1078 (tuple_start, tuple_end),
1079 );
1080 writeln!(ctx.output, ",")?;
1081 }
1082 StructKind::Struct => {
1083 write!(ctx.output, " ")?;
1084 let variant_name_start = ctx.len();
1085 write!(ctx.output, "{}", variant.name)?;
1086 let variant_name_end = ctx.len();
1087 writeln!(ctx.output, " {{")?;
1088 let struct_start = ctx.len();
1089 for field in variant.data.fields {
1090 if ctx.config.show_doc_comments {
1092 write_doc_comments(field.doc, &mut ctx.output, " ")?;
1093 }
1094 if ctx.config.show_third_party_attrs {
1096 write_field_third_party_attrs(field, &mut ctx.output, " ")?;
1097 }
1098 write!(ctx.output, " ")?;
1099 let key_start = ctx.len();
1100 write!(ctx.output, "{}", field.name)?;
1101 let key_end = ctx.len();
1102 write!(ctx.output, ": ")?;
1103 let value_start = ctx.len();
1104 write_type_name(field.shape(), &mut ctx.output)?;
1105 let value_end = ctx.len();
1106 ctx.record_field_span(
1107 vec![
1108 PathSegment::Variant(Cow::Borrowed(variant.name)),
1109 PathSegment::Field(Cow::Borrowed(field.name)),
1110 ],
1111 (key_start, key_end),
1112 (value_start, value_end),
1113 );
1114 writeln!(ctx.output, ",")?;
1115 }
1116 write!(ctx.output, " }}")?;
1117 let struct_end = ctx.len();
1118 ctx.record_field_span(
1120 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1121 (variant_name_start, variant_name_end),
1122 (struct_start, struct_end),
1123 );
1124 writeln!(ctx.output, ",")?;
1125 }
1126 }
1127 }
1128
1129 let type_end_start = ctx.len();
1130 write!(ctx.output, "}}")?;
1131 let type_end_end = ctx.len();
1132 ctx.type_end_span = Some((type_end_start, type_end_end));
1133
1134 let type_end = ctx.len();
1136 ctx.type_span = Some((type_start, type_end));
1137 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
1138
1139 Ok(())
1140}
1141
1142fn collect_nested_types<'a>(struct_type: &'a StructType, queue: &mut Vec<&'a Shape>) {
1144 for field in struct_type.fields {
1145 collect_from_shape(field.shape(), queue);
1146 }
1147}
1148
1149fn collect_from_shape<'a>(shape: &'a Shape, queue: &mut Vec<&'a Shape>) {
1151 match shape.def {
1152 Def::List(list_def) => collect_from_shape(list_def.t, queue),
1153 Def::Array(array_def) => collect_from_shape(array_def.t, queue),
1154 Def::Map(map_def) => {
1155 collect_from_shape(map_def.k, queue);
1156 collect_from_shape(map_def.v, queue);
1157 }
1158 Def::Option(option_def) => collect_from_shape(option_def.t, queue),
1159 _ => {
1160 if let Type::User(UserType::Struct(_) | UserType::Enum(_)) = &shape.ty {
1162 queue.push(shape);
1163 }
1164 }
1165 }
1166}
1167
1168fn write_facet_attrs(shape: &Shape, output: &mut String) -> core::fmt::Result {
1170 let mut attrs: Vec<String> = Vec::new();
1171
1172 if let Some(tag) = shape.get_tag_attr() {
1173 if let Some(content) = shape.get_content_attr() {
1174 attrs.push(alloc::format!("tag = \"{tag}\", content = \"{content}\""));
1175 } else {
1176 attrs.push(alloc::format!("tag = \"{tag}\""));
1177 }
1178 }
1179
1180 if shape.is_untagged() {
1181 attrs.push("untagged".into());
1182 }
1183
1184 if shape.has_deny_unknown_fields_attr() {
1185 attrs.push("deny_unknown_fields".into());
1186 }
1187
1188 if !attrs.is_empty() {
1189 writeln!(output, "#[facet({})]", attrs.join(", "))?;
1190 }
1191
1192 Ok(())
1193}
1194
1195fn write_doc_comments(doc: &[&str], output: &mut String, indent: &str) -> core::fmt::Result {
1197 for line in doc {
1198 write!(output, "{indent}")?;
1199 writeln!(output, "///{line}")?;
1200 }
1201 Ok(())
1202}
1203
1204fn write_third_party_attrs(
1206 attributes: &[Attr],
1207 output: &mut String,
1208 indent: &str,
1209) -> core::fmt::Result {
1210 let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
1212 for attr in attributes {
1213 if let Some(ns) = attr.ns {
1214 by_namespace.entry(ns).or_default().push(attr.key);
1215 }
1216 }
1217
1218 for (ns, keys) in by_namespace {
1220 write!(output, "{indent}")?;
1221 write!(output, "#[facet(")?;
1222 for (i, key) in keys.iter().enumerate() {
1223 if i > 0 {
1224 write!(output, ", ")?;
1225 }
1226 write!(output, "{ns}::{key}")?;
1227 }
1228 writeln!(output, ")]")?;
1229 }
1230 Ok(())
1231}
1232
1233fn write_field_third_party_attrs(
1235 field: &Field,
1236 output: &mut String,
1237 indent: &str,
1238) -> core::fmt::Result {
1239 write_third_party_attrs(field.attributes, output, indent)
1240}
1241
1242fn write_variant_third_party_attrs(
1244 variant: &Variant,
1245 output: &mut String,
1246 indent: &str,
1247) -> core::fmt::Result {
1248 write_third_party_attrs(variant.attributes, output, indent)
1249}
1250
1251fn write_type_name(shape: &Shape, output: &mut String) -> core::fmt::Result {
1252 match shape.def {
1253 Def::Scalar => {
1254 write!(output, "{}", shape.type_identifier)?;
1255 }
1256 Def::Pointer(_) => {
1257 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
1258 && let Def::Array(array_def) = r.target.def
1259 {
1260 write!(output, "&[")?;
1261 write_type_name(array_def.t, output)?;
1262 write!(output, "; {}]", array_def.n)?;
1263 return Ok(());
1264 }
1265 write!(output, "{}", shape.type_identifier)?;
1266 }
1267 Def::List(list_def) => {
1268 write!(output, "Vec<")?;
1269 write_type_name(list_def.t, output)?;
1270 write!(output, ">")?;
1271 }
1272 Def::Array(array_def) => {
1273 write!(output, "[")?;
1274 write_type_name(array_def.t, output)?;
1275 write!(output, "; {}]", array_def.n)?;
1276 }
1277 Def::Map(map_def) => {
1278 let map_name = if shape.type_identifier.contains("BTreeMap") {
1279 "BTreeMap"
1280 } else {
1281 "HashMap"
1282 };
1283 write!(output, "{map_name}<")?;
1284 write_type_name(map_def.k, output)?;
1285 write!(output, ", ")?;
1286 write_type_name(map_def.v, output)?;
1287 write!(output, ">")?;
1288 }
1289 Def::Option(option_def) => {
1290 write!(output, "Option<")?;
1291 write_type_name(option_def.t, output)?;
1292 write!(output, ">")?;
1293 }
1294 _ => {
1295 write!(output, "{}", shape.type_identifier)?;
1296 }
1297 }
1298 Ok(())
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use super::*;
1304 use facet::Facet;
1305
1306 #[test]
1307 fn test_simple_struct() {
1308 #[derive(Facet)]
1309 struct Simple {
1310 name: String,
1311 count: u32,
1312 }
1313
1314 let output = format_shape(Simple::SHAPE);
1315 assert!(output.contains("struct Simple"));
1316 assert!(output.contains("name: String"));
1317 assert!(output.contains("count: u32"));
1318 }
1319
1320 #[test]
1321 fn test_enum_with_tag() {
1322 #[derive(Facet)]
1323 #[repr(C)]
1324 #[facet(tag = "type")]
1325 #[allow(dead_code)]
1326 enum Tagged {
1327 A { x: i32 },
1328 B { y: String },
1329 }
1330
1331 let output = format_shape(Tagged::SHAPE);
1332 assert!(output.contains("enum Tagged"));
1333 assert!(output.contains("#[facet(tag = \"type\")]"));
1334 }
1335
1336 #[test]
1337 fn test_nested_types() {
1338 #[derive(Facet)]
1339 #[allow(dead_code)]
1340 struct Inner {
1341 value: i32,
1342 }
1343
1344 #[derive(Facet)]
1345 #[allow(dead_code)]
1346 struct Outer {
1347 inner: Inner,
1348 name: String,
1349 }
1350
1351 let output = format_shape(Outer::SHAPE);
1352 assert!(output.contains("struct Outer"), "Missing Outer: {output}");
1354 assert!(
1355 output.contains("inner: Inner"),
1356 "Missing inner field: {output}"
1357 );
1358 assert!(
1359 output.contains("struct Inner"),
1360 "Missing Inner definition: {output}"
1361 );
1362 assert!(
1363 output.contains("value: i32"),
1364 "Missing value field: {output}"
1365 );
1366 }
1367
1368 #[test]
1369 fn test_nested_in_vec() {
1370 #[derive(Facet)]
1371 #[allow(dead_code)]
1372 struct Item {
1373 id: u32,
1374 }
1375
1376 #[derive(Facet)]
1377 #[allow(dead_code)]
1378 struct Container {
1379 items: Vec<Item>,
1380 }
1381
1382 let output = format_shape(Container::SHAPE);
1383 assert!(
1385 output.contains("struct Container"),
1386 "Missing Container: {output}"
1387 );
1388 assert!(
1389 output.contains("items: Vec<Item>"),
1390 "Missing items field: {output}"
1391 );
1392 assert!(
1393 output.contains("struct Item"),
1394 "Missing Item definition: {output}"
1395 );
1396 }
1397
1398 #[test]
1399 fn test_format_shape_with_spans() {
1400 #[derive(Facet)]
1401 #[allow(dead_code)]
1402 struct Config {
1403 name: String,
1404 max_retries: u8,
1405 enabled: bool,
1406 }
1407
1408 let result = format_shape_with_spans(Config::SHAPE);
1409
1410 let name_path = vec![PathSegment::Field(Cow::Borrowed("name"))];
1412 let retries_path = vec![PathSegment::Field(Cow::Borrowed("max_retries"))];
1413 let enabled_path = vec![PathSegment::Field(Cow::Borrowed("enabled"))];
1414
1415 assert!(
1416 result.spans.contains_key(&name_path),
1417 "Missing span for 'name' field. Spans: {:?}",
1418 result.spans
1419 );
1420 assert!(
1421 result.spans.contains_key(&retries_path),
1422 "Missing span for 'max_retries' field. Spans: {:?}",
1423 result.spans
1424 );
1425 assert!(
1426 result.spans.contains_key(&enabled_path),
1427 "Missing span for 'enabled' field. Spans: {:?}",
1428 result.spans
1429 );
1430
1431 let field_span = &result.spans[&retries_path];
1433 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1434 assert_eq!(spanned_text, "u8", "Expected 'u8', got '{spanned_text}'");
1435 }
1436
1437 #[test]
1438 fn test_format_enum_with_spans() {
1439 #[derive(Facet)]
1440 #[repr(u8)]
1441 #[allow(dead_code)]
1442 enum Status {
1443 Active,
1444 Pending,
1445 Error { code: i32, message: String },
1446 }
1447
1448 let result = format_shape_with_spans(Status::SHAPE);
1449
1450 let active_path = vec![PathSegment::Variant(Cow::Borrowed("Active"))];
1452 let error_path = vec![PathSegment::Variant(Cow::Borrowed("Error"))];
1453 let error_code_path = vec![
1454 PathSegment::Variant(Cow::Borrowed("Error")),
1455 PathSegment::Field(Cow::Borrowed("code")),
1456 ];
1457
1458 assert!(
1459 result.spans.contains_key(&active_path),
1460 "Missing span for 'Active' variant. Spans: {:?}",
1461 result.spans
1462 );
1463 assert!(
1464 result.spans.contains_key(&error_path),
1465 "Missing span for 'Error' variant. Spans: {:?}",
1466 result.spans
1467 );
1468 assert!(
1469 result.spans.contains_key(&error_code_path),
1470 "Missing span for 'Error.code' field. Spans: {:?}",
1471 result.spans
1472 );
1473
1474 let field_span = &result.spans[&error_code_path];
1476 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1477 assert_eq!(spanned_text, "i32", "Expected 'i32', got '{spanned_text}'");
1478 }
1479
1480 #[test]
1481 fn test_format_with_doc_comments() {
1482 #[derive(Facet)]
1484 #[allow(dead_code)]
1485 struct Config {
1486 name: String,
1488 max_retries: u8,
1490 }
1491
1492 let output = format_shape(Config::SHAPE);
1494 assert!(
1495 output.contains("/// A configuration struct"),
1496 "Should contain struct doc comment: {output}"
1497 );
1498 assert!(
1499 output.contains("/// The name of the configuration"),
1500 "Should contain field doc comment: {output}"
1501 );
1502 assert!(
1503 output.contains("/// Maximum number of retries"),
1504 "Should contain field doc comment: {output}"
1505 );
1506
1507 let config = ShapeFormatConfig::new();
1509 let output_without = format_shape_with_config(Config::SHAPE, &config);
1510 assert!(
1511 !output_without.contains("///"),
1512 "Should not contain doc comments when disabled: {output_without}"
1513 );
1514 }
1515
1516 #[test]
1517 fn test_format_enum_with_doc_comments() {
1518 #[derive(Facet)]
1520 #[repr(u8)]
1521 #[allow(dead_code)]
1522 enum Status {
1523 Active,
1525 Error {
1527 code: i32,
1529 },
1530 }
1531
1532 let config = ShapeFormatConfig::new().with_doc_comments();
1533 let output = format_shape_with_config(Status::SHAPE, &config);
1534
1535 assert!(
1536 output.contains("/// Status of an operation"),
1537 "Should contain enum doc comment: {output}"
1538 );
1539 assert!(
1540 output.contains("/// The operation is active"),
1541 "Should contain variant doc comment: {output}"
1542 );
1543 assert!(
1544 output.contains("/// The operation failed"),
1545 "Should contain variant doc comment: {output}"
1546 );
1547 assert!(
1548 output.contains("/// Error code"),
1549 "Should contain variant field doc comment: {output}"
1550 );
1551 }
1552}