1use ahash::{AHashMap, AHashSet};
2use alef_core::ir::{ApiSurface, EnumDef, FieldDef, PrimitiveType, TypeDef, TypeRef};
3
4use crate::conversions::ConversionConfig;
5
6pub fn input_type_names(surface: &ApiSurface) -> AHashSet<String> {
19 let mut names = AHashSet::new();
20
21 for func in &surface.functions {
23 for param in &func.params {
24 collect_named_types(¶m.ty, &mut names);
25 }
26 }
27 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
29 for method in &typ.methods {
30 for param in &method.params {
31 collect_named_types(¶m.ty, &mut names);
32 }
33 }
34 }
35 for func in &surface.functions {
39 collect_named_types(&func.return_type, &mut names);
40 }
41 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
43 for method in &typ.methods {
44 collect_named_types(&method.return_type, &mut names);
45 }
46 }
47 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
52 if !typ.is_opaque && !typ.methods.is_empty() {
53 for field in &typ.fields {
54 if !field.sanitized {
55 collect_named_types(&field.ty, &mut names);
56 }
57 }
58 }
59 }
60
61 for e in &surface.enums {
64 if names.contains(&e.name) {
65 for variant in &e.variants {
66 for field in &variant.fields {
67 collect_named_types(&field.ty, &mut names);
68 }
69 }
70 }
71 }
72
73 let mut changed = true;
75 while changed {
76 changed = false;
77 let snapshot: Vec<String> = names.iter().cloned().collect();
78 for name in &snapshot {
79 if let Some(typ) = surface.types.iter().find(|t| t.name == *name) {
80 for field in &typ.fields {
81 let mut field_names = AHashSet::new();
82 collect_named_types(&field.ty, &mut field_names);
83 for n in field_names {
84 if names.insert(n) {
85 changed = true;
86 }
87 }
88 }
89 }
90 if let Some(e) = surface.enums.iter().find(|e| e.name == *name) {
92 for variant in &e.variants {
93 for field in &variant.fields {
94 let mut field_names = AHashSet::new();
95 collect_named_types(&field.ty, &mut field_names);
96 for n in field_names {
97 if names.insert(n) {
98 changed = true;
99 }
100 }
101 }
102 }
103 }
104 }
105 }
106
107 names
108}
109
110fn collect_named_types(ty: &TypeRef, out: &mut AHashSet<String>) {
112 match ty {
113 TypeRef::Named(name) => {
114 out.insert(name.clone());
115 }
116 TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named_types(inner, out),
117 TypeRef::Map(k, v) => {
118 collect_named_types(k, out);
119 collect_named_types(v, out);
120 }
121 _ => {}
122 }
123}
124
125pub fn field_references_excluded_type(ty: &TypeRef, exclude_types: &[String]) -> bool {
129 match ty {
130 TypeRef::Named(name) => exclude_types.iter().any(|e| e == name),
131 TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_references_excluded_type(inner, exclude_types),
132 TypeRef::Map(k, v) => {
133 field_references_excluded_type(k, exclude_types) || field_references_excluded_type(v, exclude_types)
134 }
135 _ => false,
136 }
137}
138
139pub(crate) fn needs_i64_cast(p: &PrimitiveType) -> bool {
141 matches!(p, PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize)
142}
143
144pub(crate) fn core_prim_str(p: &PrimitiveType) -> &'static str {
146 match p {
147 PrimitiveType::U64 => "u64",
148 PrimitiveType::Usize => "usize",
149 PrimitiveType::Isize => "isize",
150 PrimitiveType::F32 => "f32",
151 PrimitiveType::Bool => "bool",
152 PrimitiveType::U8 => "u8",
153 PrimitiveType::U16 => "u16",
154 PrimitiveType::U32 => "u32",
155 PrimitiveType::I8 => "i8",
156 PrimitiveType::I16 => "i16",
157 PrimitiveType::I32 => "i32",
158 PrimitiveType::I64 => "i64",
159 PrimitiveType::F64 => "f64",
160 }
161}
162
163pub(crate) fn binding_prim_str(p: &PrimitiveType) -> &'static str {
165 match p {
166 PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize => "i64",
167 PrimitiveType::F32 => "f64",
168 PrimitiveType::Bool => "bool",
169 PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 => "i32",
170 PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 => "i32",
171 PrimitiveType::I64 => "i64",
172 PrimitiveType::F64 => "f64",
173 }
174}
175
176pub fn core_to_binding_convertible_types(surface: &ApiSurface) -> AHashSet<String> {
180 let convertible_enums: AHashSet<&str> = surface
181 .enums
182 .iter()
183 .filter(|e| can_generate_enum_conversion_from_core(e))
184 .map(|e| e.name.as_str())
185 .collect();
186
187 let opaque_type_names: AHashSet<&str> = surface
188 .types
189 .iter()
190 .filter(|t| t.is_opaque)
191 .map(|t| t.name.as_str())
192 .collect();
193
194 let (enum_paths, type_paths) = build_rust_path_maps(surface);
196
197 let mut convertible: AHashSet<String> = surface
199 .types
200 .iter()
201 .filter(|t| !t.is_opaque)
202 .map(|t| t.name.clone())
203 .collect();
204
205 let mut changed = true;
206 while changed {
207 changed = false;
208 let snapshot: Vec<String> = convertible.iter().cloned().collect();
209 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
210 known.extend(&opaque_type_names);
211 let mut to_remove = Vec::new();
212 for type_name in &snapshot {
213 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
214 let ok = typ.fields.iter().all(|f| {
215 if f.sanitized {
216 true
217 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
218 false
219 } else {
220 is_field_convertible(&f.ty, &convertible_enums, &known)
221 }
222 });
223 if !ok {
224 to_remove.push(type_name.clone());
225 }
226 }
227 }
228 for name in to_remove {
229 if convertible.remove(&name) {
230 changed = true;
231 }
232 }
233 }
234 convertible
235}
236
237pub fn convertible_types(surface: &ApiSurface) -> AHashSet<String> {
242 let convertible_enums: AHashSet<&str> = surface
244 .enums
245 .iter()
246 .filter(|e| can_generate_enum_conversion(e))
247 .map(|e| e.name.as_str())
248 .collect();
249
250 let _all_type_names: AHashSet<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
253
254 let default_type_names: AHashSet<&str> = surface
257 .types
258 .iter()
259 .filter(|t| t.has_default)
260 .map(|t| t.name.as_str())
261 .collect();
262
263 let mut convertible: AHashSet<String> = surface
267 .types
268 .iter()
269 .filter(|t| !t.is_opaque)
270 .map(|t| t.name.clone())
271 .collect();
272
273 let opaque_type_names: AHashSet<&str> = surface
276 .types
277 .iter()
278 .filter(|t| t.is_opaque)
279 .map(|t| t.name.as_str())
280 .collect();
281
282 let (enum_paths, type_paths) = build_rust_path_maps(surface);
284
285 let mut changed = true;
290 while changed {
291 changed = false;
292 let snapshot: Vec<String> = convertible.iter().cloned().collect();
293 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
294 known.extend(&opaque_type_names);
295 let mut to_remove = Vec::new();
296 for type_name in &snapshot {
297 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
298 let ok = typ.fields.iter().all(|f| {
299 if f.sanitized {
300 sanitized_field_has_default(&f.ty, &default_type_names)
301 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
302 false
303 } else {
304 is_field_convertible(&f.ty, &convertible_enums, &known)
305 }
306 });
307 if !ok {
308 to_remove.push(type_name.clone());
309 }
310 }
311 }
312 for name in to_remove {
313 if convertible.remove(&name) {
314 changed = true;
315 }
316 }
317 }
318 convertible
319}
320
321fn sanitized_field_has_default(ty: &TypeRef, default_types: &AHashSet<&str>) -> bool {
326 match ty {
327 TypeRef::Primitive(_)
328 | TypeRef::String
329 | TypeRef::Char
330 | TypeRef::Bytes
331 | TypeRef::Path
332 | TypeRef::Unit
333 | TypeRef::Duration
334 | TypeRef::Json => true,
335 TypeRef::Optional(_) => true,
337 TypeRef::Vec(_) => true,
339 TypeRef::Map(_, _) => true,
341 TypeRef::Named(name) => {
342 if is_tuple_type_name(name) {
343 true
345 } else {
346 default_types.contains(name.as_str())
348 }
349 }
350 }
351}
352
353pub fn can_generate_conversion(typ: &TypeDef, convertible: &AHashSet<String>) -> bool {
355 convertible.contains(&typ.name)
356}
357
358pub(crate) fn is_field_convertible(
359 ty: &TypeRef,
360 convertible_enums: &AHashSet<&str>,
361 known_types: &AHashSet<&str>,
362) -> bool {
363 match ty {
364 TypeRef::Primitive(_)
365 | TypeRef::String
366 | TypeRef::Char
367 | TypeRef::Bytes
368 | TypeRef::Path
369 | TypeRef::Unit
370 | TypeRef::Duration => true,
371 TypeRef::Json => true,
372 TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_field_convertible(inner, convertible_enums, known_types),
373 TypeRef::Map(k, v) => {
374 is_field_convertible(k, convertible_enums, known_types)
375 && is_field_convertible(v, convertible_enums, known_types)
376 }
377 TypeRef::Named(name) if is_tuple_type_name(name) => true,
379 TypeRef::Named(name) => convertible_enums.contains(name.as_str()) || known_types.contains(name.as_str()),
381 }
382}
383
384fn field_has_path_mismatch(
390 field: &FieldDef,
391 enum_rust_paths: &AHashMap<&str, &str>,
392 type_rust_paths: &AHashMap<&str, &str>,
393) -> bool {
394 let name = match &field.ty {
395 TypeRef::Named(n) => n.as_str(),
396 TypeRef::Optional(inner) | TypeRef::Vec(inner) => match inner.as_ref() {
397 TypeRef::Named(n) => n.as_str(),
398 _ => return false,
399 },
400 _ => return false,
401 };
402
403 if let Some(field_path) = &field.type_rust_path {
404 if let Some(enum_path) = enum_rust_paths.get(name) {
405 if !paths_compatible(field_path, enum_path) {
406 return true;
407 }
408 }
409 if let Some(type_path) = type_rust_paths.get(name) {
410 if !paths_compatible(field_path, type_path) {
411 return true;
412 }
413 }
414 }
415 false
416}
417
418fn paths_compatible(a: &str, b: &str) -> bool {
423 if a == b {
424 return true;
425 }
426 let a_norm = a.replace('-', "_");
429 let b_norm = b.replace('-', "_");
430 if a_norm == b_norm {
431 return true;
432 }
433 if a_norm.ends_with(&b_norm) || b_norm.ends_with(&a_norm) {
435 return true;
436 }
437 let a_root = a_norm.split("::").next().unwrap_or("");
439 let b_root = b_norm.split("::").next().unwrap_or("");
440 let a_name = a_norm.rsplit("::").next().unwrap_or("");
441 let b_name = b_norm.rsplit("::").next().unwrap_or("");
442 a_root == b_root && a_name == b_name
443}
444
445fn build_rust_path_maps(surface: &ApiSurface) -> (AHashMap<&str, &str>, AHashMap<&str, &str>) {
447 let enum_paths: AHashMap<&str, &str> = surface
448 .enums
449 .iter()
450 .map(|e| (e.name.as_str(), e.rust_path.as_str()))
451 .collect();
452 let type_paths: AHashMap<&str, &str> = surface
453 .types
454 .iter()
455 .map(|t| (t.name.as_str(), t.rust_path.as_str()))
456 .collect();
457 (enum_paths, type_paths)
458}
459
460pub fn can_generate_enum_conversion(enum_def: &EnumDef) -> bool {
464 !enum_def.variants.is_empty()
465}
466
467pub fn can_generate_enum_conversion_from_core(enum_def: &EnumDef) -> bool {
470 !enum_def.variants.is_empty()
472}
473
474pub fn is_tuple_variant(fields: &[FieldDef]) -> bool {
476 !fields.is_empty()
477 && fields[0]
478 .name
479 .strip_prefix('_')
480 .is_some_and(|rest: &str| rest.chars().all(|c: char| c.is_ascii_digit()))
481}
482
483pub fn is_newtype(typ: &TypeDef) -> bool {
485 typ.fields.len() == 1 && typ.fields[0].name == "_0"
486}
487
488pub(crate) fn is_tuple_type_name(name: &str) -> bool {
491 name.starts_with('(')
492}
493
494pub fn core_type_path(typ: &TypeDef, core_import: &str) -> String {
500 let raw = if !typ.original_rust_path.is_empty() {
505 &typ.original_rust_path
506 } else {
507 &typ.rust_path
508 };
509 let path = raw.replace('-', "_");
510 if path.contains("::") {
511 path
512 } else {
513 format!("{core_import}::{}", typ.name)
514 }
515}
516
517pub fn has_sanitized_fields(typ: &TypeDef) -> bool {
519 typ.fields.iter().any(|f| f.sanitized)
520}
521
522pub fn core_enum_path(enum_def: &EnumDef, core_import: &str) -> String {
524 let path = enum_def.rust_path.replace('-', "_");
525 if path.starts_with(core_import) || path.contains("::") {
526 path
528 } else {
529 format!("{core_import}::{}", enum_def.name)
531 }
532}
533
534pub fn build_type_path_map(surface: &ApiSurface, core_import: &str) -> AHashMap<String, String> {
539 let mut map = AHashMap::new();
540 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
541 let path = typ.rust_path.replace('-', "_");
542 let resolved = if path.starts_with(core_import) {
543 path
544 } else {
545 format!("{core_import}::{}", typ.name)
546 };
547 map.insert(typ.name.clone(), resolved);
548 }
549 for en in &surface.enums {
550 let path = en.rust_path.replace('-', "_");
551 let resolved = if path.starts_with(core_import) {
552 path
553 } else {
554 format!("{core_import}::{}", en.name)
555 };
556 map.insert(en.name.clone(), resolved);
557 }
558 map
559}
560
561pub fn resolve_named_path(name: &str, core_import: &str, path_map: &AHashMap<String, String>) -> String {
566 if let Some(path) = path_map.get(name) {
567 path.clone()
568 } else {
569 format!("{core_import}::{name}")
570 }
571}
572
573pub fn binding_to_core_match_arm(binding_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
577 binding_to_core_match_arm_ext(binding_prefix, variant_name, fields, false)
578}
579
580pub fn binding_to_core_match_arm_ext_cfg(
584 binding_prefix: &str,
585 variant_name: &str,
586 fields: &[FieldDef],
587 binding_has_data: bool,
588 config: &ConversionConfig,
589) -> String {
590 use super::binding_to_core::field_conversion_to_core_cfg;
591
592 if fields.is_empty() {
593 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
594 } else if !binding_has_data {
595 if is_tuple_variant(fields) {
597 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
598 format!(
599 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
600 defaults.join(", ")
601 )
602 } else {
603 let defaults: Vec<String> = fields
604 .iter()
605 .map(|f| format!("{}: Default::default()", f.name))
606 .collect();
607 format!(
608 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
609 defaults.join(", ")
610 )
611 }
612 } else if is_tuple_variant(fields) {
613 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
614 let binding_pattern = field_names.join(", ");
615 let core_args: Vec<String> = fields
616 .iter()
617 .map(|f| {
618 let conv = field_conversion_to_core_cfg(&f.name, &f.ty, f.optional, config);
622 if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
624 let expr = expr.replace(&format!("val.{}", f.name), &f.name);
625 expr.to_string()
626 } else {
627 conv
628 }
629 })
630 .collect();
631 format!(
632 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
633 core_args.join(", ")
634 )
635 } else {
636 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
637 let pattern = field_names.join(", ");
638 let core_fields: Vec<String> = fields
639 .iter()
640 .map(|f| {
641 let conv = field_conversion_to_core_cfg(&f.name, &f.ty, f.optional, config);
645 if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
647 let expr = expr.replace(&format!("val.{}", f.name), &f.name);
648 format!("{}: {}", f.name, expr)
649 } else {
650 conv
651 }
652 })
653 .collect();
654 format!(
655 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
656 core_fields.join(", ")
657 )
658 }
659}
660
661pub fn binding_to_core_match_arm_ext(
662 binding_prefix: &str,
663 variant_name: &str,
664 fields: &[FieldDef],
665 binding_has_data: bool,
666) -> String {
667 if fields.is_empty() {
668 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
669 } else if !binding_has_data {
670 if is_tuple_variant(fields) {
672 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
673 format!(
674 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
675 defaults.join(", ")
676 )
677 } else {
678 let defaults: Vec<String> = fields
679 .iter()
680 .map(|f| format!("{}: Default::default()", f.name))
681 .collect();
682 format!(
683 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
684 defaults.join(", ")
685 )
686 }
687 } else if is_tuple_variant(fields) {
688 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
690 let binding_pattern = field_names.join(", ");
691 let core_args: Vec<String> = fields
693 .iter()
694 .map(|f| {
695 let name = &f.name;
696 let expr = if matches!(&f.ty, TypeRef::Named(_)) {
697 format!("{name}.into()")
698 } else if f.sanitized {
699 format!("serde_json::from_str(&{name}).unwrap_or_default()")
700 } else {
701 name.clone()
702 };
703 if f.is_boxed { format!("Box::new({expr})") } else { expr }
704 })
705 .collect();
706 format!(
707 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
708 core_args.join(", ")
709 )
710 } else {
711 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
713 let pattern = field_names.join(", ");
714 let core_fields: Vec<String> = fields
715 .iter()
716 .map(|f| {
717 if matches!(&f.ty, TypeRef::Named(_)) {
718 format!("{}: {}.into()", f.name, f.name)
719 } else if f.sanitized {
720 format!("{}: serde_json::from_str(&{}).unwrap_or_default()", f.name, f.name)
724 } else {
725 format!("{0}: {0}", f.name)
726 }
727 })
728 .collect();
729 format!(
730 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
731 core_fields.join(", ")
732 )
733 }
734}
735
736pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
740 core_to_binding_match_arm_ext(core_prefix, variant_name, fields, false)
741}
742
743pub fn core_to_binding_match_arm_ext_cfg(
747 core_prefix: &str,
748 variant_name: &str,
749 fields: &[FieldDef],
750 binding_has_data: bool,
751 config: &ConversionConfig,
752) -> String {
753 use super::core_to_binding::field_conversion_from_core_cfg;
754 use ahash::AHashSet;
755
756 if fields.is_empty() {
757 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
758 } else if !binding_has_data {
759 if is_tuple_variant(fields) {
761 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
762 } else {
763 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
764 }
765 } else if is_tuple_variant(fields) {
766 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
767 let core_pattern = field_names.join(", ");
768 let binding_fields: Vec<String> = fields
769 .iter()
770 .map(|f| {
771 let conv =
775 field_conversion_from_core_cfg(&f.name, &f.ty, f.optional, f.sanitized, &AHashSet::new(), config);
776 if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
778 let mut expr = expr.replace(&format!("val.{}", f.name), &f.name);
779 if f.is_boxed {
781 expr = expr.replace(&format!("{}.into()", f.name), &format!("(*{}).into()", f.name));
782 }
783 format!("{}: {}", f.name, expr)
784 } else {
785 conv
786 }
787 })
788 .collect();
789 format!(
790 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
791 binding_fields.join(", ")
792 )
793 } else {
794 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
795 let pattern = field_names.join(", ");
796 let binding_fields: Vec<String> = fields
797 .iter()
798 .map(|f| {
799 let conv =
803 field_conversion_from_core_cfg(&f.name, &f.ty, f.optional, f.sanitized, &AHashSet::new(), config);
804 if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
806 let expr = expr.replace(&format!("val.{}", f.name), &f.name);
807 format!("{}: {}", f.name, expr)
808 } else {
809 conv
810 }
811 })
812 .collect();
813 format!(
814 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
815 binding_fields.join(", ")
816 )
817 }
818}
819
820pub fn core_to_binding_match_arm_ext(
821 core_prefix: &str,
822 variant_name: &str,
823 fields: &[FieldDef],
824 binding_has_data: bool,
825) -> String {
826 if fields.is_empty() {
827 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
828 } else if !binding_has_data {
829 if is_tuple_variant(fields) {
831 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
832 } else {
833 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
834 }
835 } else if is_tuple_variant(fields) {
836 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
838 let core_pattern = field_names.join(", ");
839 let binding_fields: Vec<String> = fields
841 .iter()
842 .map(|f| {
843 let name = &f.name;
844 let expr = if f.is_boxed && matches!(&f.ty, TypeRef::Named(_)) {
845 format!("(*{name}).into()")
846 } else if f.is_boxed {
847 format!("*{name}")
848 } else if matches!(&f.ty, TypeRef::Named(_)) {
849 format!("{name}.into()")
850 } else if f.sanitized {
851 format!("serde_json::to_string(&{name}).unwrap_or_default()")
852 } else {
853 name.clone()
854 };
855 format!("{name}: {expr}")
856 })
857 .collect();
858 format!(
859 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
860 binding_fields.join(", ")
861 )
862 } else {
863 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
864 let pattern = field_names.join(", ");
865 let binding_fields: Vec<String> = fields
866 .iter()
867 .map(|f| {
868 if matches!(&f.ty, TypeRef::Named(_)) {
869 format!("{}: {}.into()", f.name, f.name)
870 } else if f.sanitized {
871 format!("{}: serde_json::to_string(&{}).unwrap_or_default()", f.name, f.name)
875 } else {
876 format!("{0}: {0}", f.name)
877 }
878 })
879 .collect();
880 format!(
881 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
882 binding_fields.join(", ")
883 )
884 }
885}