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 data_enum_names: AHashSet<&str> = surface
199 .enums
200 .iter()
201 .filter(|e| e.variants.iter().any(|v| !v.fields.is_empty()))
202 .map(|e| e.name.as_str())
203 .collect();
204
205 let (enum_paths, type_paths) = build_rust_path_maps(surface);
207
208 let mut convertible: AHashSet<String> = surface
210 .types
211 .iter()
212 .filter(|t| !t.is_opaque)
213 .map(|t| t.name.clone())
214 .collect();
215
216 let mut changed = true;
217 while changed {
218 changed = false;
219 let snapshot: Vec<String> = convertible.iter().cloned().collect();
220 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
221 known.extend(&opaque_type_names);
222 known.extend(&data_enum_names);
223 let mut to_remove = Vec::new();
224 for type_name in &snapshot {
225 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
226 let ok = typ.fields.iter().all(|f| {
227 if f.sanitized {
228 true
229 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
230 false
231 } else {
232 is_field_convertible(&f.ty, &convertible_enums, &known)
233 }
234 });
235 if !ok {
236 to_remove.push(type_name.clone());
237 }
238 }
239 }
240 for name in to_remove {
241 if convertible.remove(&name) {
242 changed = true;
243 }
244 }
245 }
246 convertible
247}
248
249pub fn convertible_types(surface: &ApiSurface) -> AHashSet<String> {
254 let convertible_enums: AHashSet<&str> = surface
256 .enums
257 .iter()
258 .filter(|e| can_generate_enum_conversion(e))
259 .map(|e| e.name.as_str())
260 .collect();
261
262 let _all_type_names: AHashSet<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
265
266 let default_type_names: AHashSet<&str> = surface
269 .types
270 .iter()
271 .filter(|t| t.has_default)
272 .map(|t| t.name.as_str())
273 .collect();
274
275 let mut convertible: AHashSet<String> = surface
279 .types
280 .iter()
281 .filter(|t| !t.is_opaque)
282 .map(|t| t.name.clone())
283 .collect();
284
285 let opaque_type_names: AHashSet<&str> = surface
288 .types
289 .iter()
290 .filter(|t| t.is_opaque)
291 .map(|t| t.name.as_str())
292 .collect();
293
294 let data_enum_names: AHashSet<&str> = surface
297 .enums
298 .iter()
299 .filter(|e| e.variants.iter().any(|v| !v.fields.is_empty()))
300 .map(|e| e.name.as_str())
301 .collect();
302
303 let (enum_paths, type_paths) = build_rust_path_maps(surface);
305
306 let mut changed = true;
311 while changed {
312 changed = false;
313 let snapshot: Vec<String> = convertible.iter().cloned().collect();
314 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
315 known.extend(&opaque_type_names);
316 known.extend(&data_enum_names);
317 let mut to_remove = Vec::new();
318 for type_name in &snapshot {
319 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
320 let ok = typ.fields.iter().all(|f| {
321 if f.sanitized {
322 f.optional || sanitized_field_has_default(&f.ty, &default_type_names)
325 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
326 false
327 } else {
328 is_field_convertible(&f.ty, &convertible_enums, &known)
329 }
330 });
331 if !ok {
332 to_remove.push(type_name.clone());
333 }
334 }
335 }
336 for name in to_remove {
337 if convertible.remove(&name) {
338 changed = true;
339 }
340 }
341 }
342 convertible
343}
344
345fn sanitized_field_has_default(ty: &TypeRef, default_types: &AHashSet<&str>) -> bool {
350 match ty {
351 TypeRef::Primitive(_)
352 | TypeRef::String
353 | TypeRef::Char
354 | TypeRef::Bytes
355 | TypeRef::Path
356 | TypeRef::Unit
357 | TypeRef::Duration
358 | TypeRef::Json => true,
359 TypeRef::Optional(_) => true,
361 TypeRef::Vec(_) => true,
363 TypeRef::Map(_, _) => true,
365 TypeRef::Named(name) => {
366 if is_tuple_type_name(name) {
367 true
369 } else {
370 default_types.contains(name.as_str())
372 }
373 }
374 }
375}
376
377pub fn can_generate_conversion(typ: &TypeDef, convertible: &AHashSet<String>) -> bool {
379 convertible.contains(&typ.name)
380}
381
382pub(crate) fn is_field_convertible(
383 ty: &TypeRef,
384 convertible_enums: &AHashSet<&str>,
385 known_types: &AHashSet<&str>,
386) -> bool {
387 match ty {
388 TypeRef::Primitive(_)
389 | TypeRef::String
390 | TypeRef::Char
391 | TypeRef::Bytes
392 | TypeRef::Path
393 | TypeRef::Unit
394 | TypeRef::Duration => true,
395 TypeRef::Json => true,
396 TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_field_convertible(inner, convertible_enums, known_types),
397 TypeRef::Map(k, v) => {
398 is_field_convertible(k, convertible_enums, known_types)
399 && is_field_convertible(v, convertible_enums, known_types)
400 }
401 TypeRef::Named(name) if is_tuple_type_name(name) => true,
403 TypeRef::Named(name) => convertible_enums.contains(name.as_str()) || known_types.contains(name.as_str()),
405 }
406}
407
408fn field_has_path_mismatch(
414 field: &FieldDef,
415 enum_rust_paths: &AHashMap<&str, &str>,
416 type_rust_paths: &AHashMap<&str, &str>,
417) -> bool {
418 let name = match &field.ty {
419 TypeRef::Named(n) => n.as_str(),
420 TypeRef::Optional(inner) | TypeRef::Vec(inner) => match inner.as_ref() {
421 TypeRef::Named(n) => n.as_str(),
422 _ => return false,
423 },
424 _ => return false,
425 };
426
427 if let Some(field_path) = &field.type_rust_path {
428 if let Some(enum_path) = enum_rust_paths.get(name) {
429 if !paths_compatible(field_path, enum_path) {
430 return true;
431 }
432 }
433 if let Some(type_path) = type_rust_paths.get(name) {
434 if !paths_compatible(field_path, type_path) {
435 return true;
436 }
437 }
438 }
439 false
440}
441
442fn paths_compatible(a: &str, b: &str) -> bool {
447 if a == b {
448 return true;
449 }
450 let a_norm = a.replace('-', "_");
453 let b_norm = b.replace('-', "_");
454 if a_norm == b_norm {
455 return true;
456 }
457 if a_norm.ends_with(&b_norm) || b_norm.ends_with(&a_norm) {
459 return true;
460 }
461 let a_root = a_norm.split("::").next().unwrap_or("");
463 let b_root = b_norm.split("::").next().unwrap_or("");
464 let a_name = a_norm.rsplit("::").next().unwrap_or("");
465 let b_name = b_norm.rsplit("::").next().unwrap_or("");
466 a_root == b_root && a_name == b_name
467}
468
469fn build_rust_path_maps(surface: &ApiSurface) -> (AHashMap<&str, &str>, AHashMap<&str, &str>) {
471 let enum_paths: AHashMap<&str, &str> = surface
472 .enums
473 .iter()
474 .map(|e| (e.name.as_str(), e.rust_path.as_str()))
475 .collect();
476 let type_paths: AHashMap<&str, &str> = surface
477 .types
478 .iter()
479 .map(|t| (t.name.as_str(), t.rust_path.as_str()))
480 .collect();
481 (enum_paths, type_paths)
482}
483
484pub fn can_generate_enum_conversion(enum_def: &EnumDef) -> bool {
488 !enum_def.variants.is_empty()
489}
490
491pub fn can_generate_enum_conversion_from_core(enum_def: &EnumDef) -> bool {
494 !enum_def.variants.is_empty()
496}
497
498pub fn is_tuple_variant(fields: &[FieldDef]) -> bool {
500 !fields.is_empty()
501 && fields[0]
502 .name
503 .strip_prefix('_')
504 .is_some_and(|rest: &str| rest.chars().all(|c: char| c.is_ascii_digit()))
505}
506
507pub fn is_newtype(typ: &TypeDef) -> bool {
509 typ.fields.len() == 1 && typ.fields[0].name == "_0"
510}
511
512pub(crate) fn is_tuple_type_name(name: &str) -> bool {
515 name.starts_with('(')
516}
517
518pub fn core_type_path(typ: &TypeDef, core_import: &str) -> String {
524 core_type_path_remapped(typ, core_import, &[])
525}
526
527pub fn core_type_path_remapped(typ: &TypeDef, core_import: &str, remaps: &[(&str, &str)]) -> String {
536 let path = typ.rust_path.replace('-', "_");
541 if path.contains("::") {
542 apply_crate_remaps(&path, remaps)
543 } else {
544 format!("{core_import}::{}", typ.name)
545 }
546}
547
548pub fn apply_crate_remaps(path: &str, remaps: &[(&str, &str)]) -> String {
554 if remaps.is_empty() {
555 return path.to_string();
556 }
557 if let Some(sep) = path.find("::") {
558 let leading = &path[..sep];
559 if let Some(&(_, override_crate)) = remaps.iter().find(|(orig, _)| *orig == leading) {
560 return format!("{override_crate}{}", &path[sep..]);
561 }
562 }
563 path.to_string()
564}
565
566pub fn has_sanitized_fields(typ: &TypeDef) -> bool {
568 typ.fields.iter().any(|f| f.sanitized)
569}
570
571pub fn core_enum_path(enum_def: &EnumDef, core_import: &str) -> String {
573 core_enum_path_remapped(enum_def, core_import, &[])
574}
575
576pub fn core_enum_path_remapped(enum_def: &EnumDef, core_import: &str, remaps: &[(&str, &str)]) -> String {
579 let path = enum_def.rust_path.replace('-', "_");
580 if path.starts_with(core_import) || path.contains("::") {
581 apply_crate_remaps(&path, remaps)
583 } else {
584 format!("{core_import}::{}", enum_def.name)
586 }
587}
588
589pub fn build_type_path_map(surface: &ApiSurface, core_import: &str) -> AHashMap<String, String> {
594 let mut map = AHashMap::new();
595 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
596 let path = typ.rust_path.replace('-', "_");
597 let resolved = if path.starts_with(core_import) {
598 path
599 } else {
600 format!("{core_import}::{}", typ.name)
601 };
602 map.insert(typ.name.clone(), resolved);
603 }
604 for en in &surface.enums {
605 let path = en.rust_path.replace('-', "_");
606 let resolved = if path.starts_with(core_import) {
607 path
608 } else {
609 format!("{core_import}::{}", en.name)
610 };
611 map.insert(en.name.clone(), resolved);
612 }
613 map
614}
615
616pub fn resolve_named_path(name: &str, core_import: &str, path_map: &AHashMap<String, String>) -> String {
621 if let Some(path) = path_map.get(name) {
622 path.clone()
623 } else {
624 format!("{core_import}::{name}")
625 }
626}
627
628pub fn binding_to_core_match_arm(binding_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
632 binding_to_core_match_arm_ext(binding_prefix, variant_name, fields, false)
633}
634
635pub fn binding_to_core_match_arm_ext_cfg(
639 binding_prefix: &str,
640 variant_name: &str,
641 fields: &[FieldDef],
642 binding_has_data: bool,
643 config: &ConversionConfig,
644) -> String {
645 use super::binding_to_core::field_conversion_to_core_cfg;
646
647 if fields.is_empty() {
648 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
649 } else if !binding_has_data {
650 if is_tuple_variant(fields) {
652 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
653 format!(
654 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
655 defaults.join(", ")
656 )
657 } else {
658 let defaults: Vec<String> = fields
659 .iter()
660 .map(|f| format!("{}: Default::default()", f.name))
661 .collect();
662 format!(
663 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
664 defaults.join(", ")
665 )
666 }
667 } else if is_tuple_variant(fields) {
668 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
669 let binding_pattern = field_names.join(", ");
670 let core_args: Vec<String> = fields
671 .iter()
672 .map(|f| {
673 let name = &f.name;
674 if f.sanitized {
676 let expr = if let TypeRef::Vec(_) = &f.ty {
677 format!("{name}.iter().filter_map(|s| serde_json::from_str(s).ok()).collect()")
680 } else {
681 format!("serde_json::from_str(&{name}).unwrap_or_default()")
682 };
683 return if f.is_boxed { format!("Box::new({expr})") } else { expr };
684 }
685 if !config.exclude_types.is_empty() && field_references_excluded_type(&f.ty, config.exclude_types) {
688 let expr = format!("serde_json::from_str(&{name}).unwrap_or_default()");
689 return if f.is_boxed { format!("Box::new({expr})") } else { expr };
690 }
691 let conv = field_conversion_to_core_cfg(name, &f.ty, f.optional, config);
695 let expr = if let Some(expr) = conv.strip_prefix(&format!("{name}: ")) {
697 let expr = expr.replace(&format!("val.{name}"), name);
698 expr.to_string()
699 } else {
700 conv
701 };
702 if f.is_boxed { format!("Box::new({expr})") } else { expr }
703 })
704 .collect();
705 format!(
706 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
707 core_args.join(", ")
708 )
709 } else {
710 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
711 let pattern = field_names.join(", ");
712 let core_fields: Vec<String> = fields
713 .iter()
714 .map(|f| {
715 if f.sanitized {
718 if let TypeRef::Vec(_) = &f.ty {
722 return format!(
723 "{}: {}.iter().filter_map(|s| serde_json::from_str(s).ok()).collect()",
724 f.name, f.name
725 );
726 }
727 return format!("{}: serde_json::from_str(&{}).unwrap_or_default()", f.name, f.name);
728 }
729 let conv = field_conversion_to_core_cfg(&f.name, &f.ty, f.optional, config);
733 if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
735 let expr = expr.replace(&format!("val.{}", f.name), &f.name);
736 format!("{}: {}", f.name, expr)
737 } else {
738 conv
739 }
740 })
741 .collect();
742 format!(
743 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
744 core_fields.join(", ")
745 )
746 }
747}
748
749pub fn binding_to_core_match_arm_ext(
750 binding_prefix: &str,
751 variant_name: &str,
752 fields: &[FieldDef],
753 binding_has_data: bool,
754) -> String {
755 if fields.is_empty() {
756 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
757 } else if !binding_has_data {
758 if is_tuple_variant(fields) {
760 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
761 format!(
762 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
763 defaults.join(", ")
764 )
765 } else {
766 let defaults: Vec<String> = fields
767 .iter()
768 .map(|f| format!("{}: Default::default()", f.name))
769 .collect();
770 format!(
771 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
772 defaults.join(", ")
773 )
774 }
775 } else if is_tuple_variant(fields) {
776 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
778 let binding_pattern = field_names.join(", ");
779 let core_args: Vec<String> = fields
781 .iter()
782 .map(|f| {
783 let name = &f.name;
784 let expr = if matches!(&f.ty, TypeRef::Named(_)) {
785 format!("{name}.into()")
786 } else if f.sanitized {
787 format!("serde_json::from_str(&{name}).unwrap_or_default()")
788 } else {
789 name.clone()
790 };
791 if f.is_boxed { format!("Box::new({expr})") } else { expr }
792 })
793 .collect();
794 format!(
795 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
796 core_args.join(", ")
797 )
798 } else {
799 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
801 let pattern = field_names.join(", ");
802 let core_fields: Vec<String> = fields
803 .iter()
804 .map(|f| {
805 if matches!(&f.ty, TypeRef::Named(_)) {
806 format!("{}: {}.into()", f.name, f.name)
807 } else if f.sanitized {
808 format!("{}: serde_json::from_str(&{}).unwrap_or_default()", f.name, f.name)
812 } else {
813 format!("{0}: {0}", f.name)
814 }
815 })
816 .collect();
817 format!(
818 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
819 core_fields.join(", ")
820 )
821 }
822}
823
824pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
828 core_to_binding_match_arm_ext(core_prefix, variant_name, fields, false)
829}
830
831pub fn core_to_binding_match_arm_ext_cfg(
835 core_prefix: &str,
836 variant_name: &str,
837 fields: &[FieldDef],
838 binding_has_data: bool,
839 config: &ConversionConfig,
840) -> String {
841 use super::core_to_binding::field_conversion_from_core_cfg;
842 use ahash::AHashSet;
843
844 if fields.is_empty() {
845 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
846 } else if !binding_has_data {
847 if is_tuple_variant(fields) {
849 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
850 } else {
851 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
852 }
853 } else if is_tuple_variant(fields) {
854 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
855 let core_pattern = field_names.join(", ");
856 let binding_fields: Vec<String> = fields
857 .iter()
858 .map(|f| {
859 let conv =
863 field_conversion_from_core_cfg(&f.name, &f.ty, f.optional, f.sanitized, &AHashSet::new(), config);
864 if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
866 let mut expr = expr.replace(&format!("val.{}", f.name), &f.name);
867 if f.is_boxed {
869 expr = expr.replace(&format!("{}.into()", f.name), &format!("(*{}).into()", f.name));
870 }
871 format!("{}: {}", f.name, expr)
872 } else {
873 conv
874 }
875 })
876 .collect();
877 format!(
878 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
879 binding_fields.join(", ")
880 )
881 } else {
882 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
883 let pattern = field_names.join(", ");
884 let binding_fields: Vec<String> = fields
885 .iter()
886 .map(|f| {
887 let conv =
891 field_conversion_from_core_cfg(&f.name, &f.ty, f.optional, f.sanitized, &AHashSet::new(), config);
892 if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
894 let expr = expr.replace(&format!("val.{}", f.name), &f.name);
895 format!("{}: {}", f.name, expr)
896 } else {
897 conv
898 }
899 })
900 .collect();
901 format!(
902 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
903 binding_fields.join(", ")
904 )
905 }
906}
907
908pub fn core_to_binding_match_arm_ext(
909 core_prefix: &str,
910 variant_name: &str,
911 fields: &[FieldDef],
912 binding_has_data: bool,
913) -> String {
914 if fields.is_empty() {
915 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
916 } else if !binding_has_data {
917 if is_tuple_variant(fields) {
919 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
920 } else {
921 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
922 }
923 } else if is_tuple_variant(fields) {
924 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
926 let core_pattern = field_names.join(", ");
927 let binding_fields: Vec<String> = fields
929 .iter()
930 .map(|f| {
931 let name = &f.name;
932 let expr = if f.is_boxed && matches!(&f.ty, TypeRef::Named(_)) {
933 format!("(*{name}).into()")
934 } else if f.is_boxed {
935 format!("*{name}")
936 } else if matches!(&f.ty, TypeRef::Named(_)) {
937 format!("{name}.into()")
938 } else if f.sanitized {
939 format!("serde_json::to_string(&{name}).unwrap_or_default()")
940 } else {
941 name.clone()
942 };
943 format!("{name}: {expr}")
944 })
945 .collect();
946 format!(
947 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
948 binding_fields.join(", ")
949 )
950 } else {
951 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
952 let pattern = field_names.join(", ");
953 let binding_fields: Vec<String> = fields
954 .iter()
955 .map(|f| {
956 if matches!(&f.ty, TypeRef::Named(_)) {
957 format!("{}: {}.into()", f.name, f.name)
958 } else if f.sanitized {
959 format!("{}: serde_json::to_string(&{}).unwrap_or_default()", f.name, f.name)
963 } else {
964 format!("{0}: {0}", f.name)
965 }
966 })
967 .collect();
968 format!(
969 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
970 binding_fields.join(", ")
971 )
972 }
973}