1mod binding_to_core;
2mod core_to_binding;
3mod enums;
4pub(crate) mod helpers;
5
6use ahash::AHashSet;
7
8#[derive(Default, Clone)]
11pub struct ConversionConfig<'a> {
12 pub type_name_prefix: &'a str,
14 pub cast_large_ints_to_i64: bool,
16 pub enum_string_names: Option<&'a AHashSet<String>>,
19 pub map_uses_jsvalue: bool,
23 pub cast_f32_to_f64: bool,
25 pub optionalize_defaults: bool,
29 pub json_to_string: bool,
33 pub json_as_value: bool,
38 pub include_cfg_metadata: bool,
41 pub option_duration_on_defaults: bool,
47 pub binding_enums_have_data: bool,
50 pub exclude_types: &'a [String],
54 pub vec_named_to_string: bool,
59 pub map_as_string: bool,
63 pub opaque_types: Option<&'a AHashSet<String>>,
68 pub from_binding_skip_types: &'a [String],
72 pub source_crate_remaps: &'a [(&'a str, &'a str)],
80 pub binding_field_renames: Option<&'a std::collections::HashMap<String, String>>,
88 pub cast_uints_to_i32: bool,
92 pub cast_large_ints_to_f64: bool,
96 pub untagged_data_enum_names: Option<&'a AHashSet<String>>,
106 pub never_skip_cfg_field_names: &'a [String],
110 pub trait_bridge_arc_wrapper_field_names: &'a [String],
116 pub strip_cfg_fields_from_binding_struct: bool,
126}
127
128impl<'a> ConversionConfig<'a> {
129 pub fn binding_field_name<'b>(&self, type_name: &str, field_name: &'b str) -> &'b str
134 where
135 'a: 'b,
136 {
137 let _ = type_name;
142 field_name
143 }
144
145 pub fn trait_bridge_field_is_arc_wrapper(&self, field_name: &str) -> bool {
149 self.trait_bridge_arc_wrapper_field_names
150 .iter()
151 .any(|n| n == field_name)
152 }
153
154 pub fn binding_field_name_owned(&self, type_name: &str, field_name: &str) -> String {
157 if let Some(map) = self.binding_field_renames {
158 let key = format!("{type_name}.{field_name}");
159 if let Some(renamed) = map.get(&key) {
160 return renamed.clone();
161 }
162 }
163 field_name.to_string()
164 }
165}
166
167pub use binding_to_core::{
169 apply_core_wrapper_to_core, field_conversion_to_core, field_conversion_to_core_cfg, gen_from_binding_to_core,
170 gen_from_binding_to_core_cfg,
171};
172pub use core_to_binding::{
173 field_conversion_from_core, field_conversion_from_core_cfg, gen_from_core_to_binding, gen_from_core_to_binding_cfg,
174};
175pub use enums::{
176 gen_enum_from_binding_to_core, gen_enum_from_binding_to_core_cfg, gen_enum_from_core_to_binding,
177 gen_enum_from_core_to_binding_cfg,
178};
179pub use helpers::{
180 apply_crate_remaps, binding_to_core_match_arm, build_type_path_map, can_generate_conversion,
181 can_generate_enum_conversion, can_generate_enum_conversion_from_core, convertible_types, core_enum_path,
182 core_enum_path_remapped, core_to_binding_convertible_types, core_to_binding_match_arm, core_type_path,
183 core_type_path_remapped, field_references_excluded_type, has_sanitized_fields, input_type_names, is_tuple_variant,
184 resolve_named_path,
185};
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use alef_core::ir::*;
191
192 fn simple_type() -> TypeDef {
193 TypeDef {
194 name: "Config".to_string(),
195 rust_path: "my_crate::Config".to_string(),
196 original_rust_path: String::new(),
197 fields: vec![
198 FieldDef {
199 name: "name".into(),
200 ty: TypeRef::String,
201 optional: false,
202 default: None,
203 doc: String::new(),
204 sanitized: false,
205 is_boxed: false,
206 type_rust_path: None,
207 cfg: None,
208 typed_default: None,
209 core_wrapper: CoreWrapper::None,
210 vec_inner_core_wrapper: CoreWrapper::None,
211 newtype_wrapper: None,
212 serde_rename: None,
213 serde_flatten: false,
214 },
215 FieldDef {
216 name: "timeout".into(),
217 ty: TypeRef::Primitive(PrimitiveType::U64),
218 optional: true,
219 default: None,
220 doc: String::new(),
221 sanitized: false,
222 is_boxed: false,
223 type_rust_path: None,
224 cfg: None,
225 typed_default: None,
226 core_wrapper: CoreWrapper::None,
227 vec_inner_core_wrapper: CoreWrapper::None,
228 newtype_wrapper: None,
229 serde_rename: None,
230 serde_flatten: false,
231 },
232 FieldDef {
233 name: "backend".into(),
234 ty: TypeRef::Named("Backend".into()),
235 optional: true,
236 default: None,
237 doc: String::new(),
238 sanitized: false,
239 is_boxed: false,
240 type_rust_path: None,
241 cfg: None,
242 typed_default: None,
243 core_wrapper: CoreWrapper::None,
244 vec_inner_core_wrapper: CoreWrapper::None,
245 newtype_wrapper: None,
246 serde_rename: None,
247 serde_flatten: false,
248 },
249 ],
250 methods: vec![],
251 is_opaque: false,
252 is_clone: true,
253 is_copy: false,
254 is_trait: false,
255 has_default: false,
256 has_stripped_cfg_fields: false,
257 is_return_type: false,
258 serde_rename_all: None,
259 has_serde: false,
260 super_traits: vec![],
261 doc: String::new(),
262 cfg: None,
263 }
264 }
265
266 fn simple_enum() -> EnumDef {
267 EnumDef {
268 name: "Backend".to_string(),
269 rust_path: "my_crate::Backend".to_string(),
270 original_rust_path: String::new(),
271 variants: vec![
272 EnumVariant {
273 name: "Cpu".into(),
274 fields: vec![],
275 is_tuple: false,
276 doc: String::new(),
277 is_default: false,
278 serde_rename: None,
279 },
280 EnumVariant {
281 name: "Gpu".into(),
282 fields: vec![],
283 is_tuple: false,
284 doc: String::new(),
285 is_default: false,
286 serde_rename: None,
287 },
288 ],
289 doc: String::new(),
290 cfg: None,
291 is_copy: false,
292 has_serde: false,
293 serde_tag: None,
294 serde_untagged: false,
295 serde_rename_all: None,
296 }
297 }
298
299 #[test]
300 fn test_from_binding_to_core() {
301 let typ = simple_type();
302 let result = gen_from_binding_to_core(&typ, "my_crate");
303 assert!(result.contains("impl From<Config> for my_crate::Config"));
304 assert!(result.contains("name: val.name"));
305 assert!(result.contains("timeout: val.timeout"));
306 assert!(result.contains("backend: val.backend.map(Into::into)"));
307 }
308
309 #[test]
310 fn test_from_core_to_binding() {
311 let typ = simple_type();
312 let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
313 assert!(result.contains("impl From<my_crate::Config> for Config"));
314 }
315
316 #[test]
317 fn test_enum_from_binding_to_core() {
318 let enum_def = simple_enum();
319 let result = gen_enum_from_binding_to_core(&enum_def, "my_crate");
320 assert!(result.contains("impl From<Backend> for my_crate::Backend"));
321 assert!(result.contains("Backend::Cpu => Self::Cpu"));
322 assert!(result.contains("Backend::Gpu => Self::Gpu"));
323 }
324
325 #[test]
326 fn test_enum_from_core_to_binding() {
327 let enum_def = simple_enum();
328 let result = gen_enum_from_core_to_binding(&enum_def, "my_crate");
329 assert!(result.contains("impl From<my_crate::Backend> for Backend"));
330 assert!(result.contains("my_crate::Backend::Cpu => Self::Cpu"));
331 assert!(result.contains("my_crate::Backend::Gpu => Self::Gpu"));
332 }
333
334 #[test]
335 fn test_from_binding_to_core_with_cfg_gated_field() {
336 let mut typ = simple_type();
338 typ.has_stripped_cfg_fields = true;
339 typ.fields.push(FieldDef {
340 name: "layout".into(),
341 ty: TypeRef::String,
342 optional: false,
343 default: None,
344 doc: String::new(),
345 sanitized: false,
346 is_boxed: false,
347 type_rust_path: None,
348 cfg: Some("feature = \"layout-detection\"".into()),
349 typed_default: None,
350 core_wrapper: CoreWrapper::None,
351 vec_inner_core_wrapper: CoreWrapper::None,
352 newtype_wrapper: None,
353 serde_rename: None,
354 serde_flatten: false,
355 });
356
357 let result = gen_from_binding_to_core(&typ, "my_crate");
358
359 assert!(result.contains("impl From<Config> for my_crate::Config"));
361 assert!(result.contains("name: val.name"));
363 assert!(result.contains("timeout: val.timeout"));
364 assert!(result.contains("layout: val.layout"));
367 }
368
369 #[test]
370 fn test_from_core_to_binding_with_cfg_gated_field() {
371 let mut typ = simple_type();
373 typ.fields.push(FieldDef {
374 name: "layout".into(),
375 ty: TypeRef::String,
376 optional: false,
377 default: None,
378 doc: String::new(),
379 sanitized: false,
380 is_boxed: false,
381 type_rust_path: None,
382 cfg: Some("feature = \"layout-detection\"".into()),
383 typed_default: None,
384 core_wrapper: CoreWrapper::None,
385 vec_inner_core_wrapper: CoreWrapper::None,
386 newtype_wrapper: None,
387 serde_rename: None,
388 serde_flatten: false,
389 });
390
391 let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
392
393 assert!(result.contains("impl From<my_crate::Config> for Config"));
395 assert!(result.contains("name: val.name"));
397 assert!(result.contains("layout: val.layout"));
399 }
400
401 #[test]
402 fn test_field_conversion_from_core_map_named_non_optional() {
403 let result = field_conversion_from_core(
405 "tags",
406 &TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::Named("Tag".into()))),
407 false,
408 false,
409 &AHashSet::new(),
410 );
411 assert_eq!(
412 result,
413 "tags: val.tags.into_iter().map(|(k, v)| (k, v.into())).collect()"
414 );
415 }
416
417 #[test]
418 fn test_field_conversion_from_core_option_map_named() {
419 let result = field_conversion_from_core(
421 "tags",
422 &TypeRef::Optional(Box::new(TypeRef::Map(
423 Box::new(TypeRef::String),
424 Box::new(TypeRef::Named("Tag".into())),
425 ))),
426 false,
427 false,
428 &AHashSet::new(),
429 );
430 assert_eq!(
431 result,
432 "tags: val.tags.map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect())"
433 );
434 }
435
436 #[test]
437 fn test_field_conversion_from_core_vec_named_non_optional() {
438 let result = field_conversion_from_core(
440 "items",
441 &TypeRef::Vec(Box::new(TypeRef::Named("Item".into()))),
442 false,
443 false,
444 &AHashSet::new(),
445 );
446 assert_eq!(result, "items: val.items.into_iter().map(Into::into).collect()");
447 }
448
449 #[test]
450 fn test_field_conversion_from_core_option_vec_named() {
451 let result = field_conversion_from_core(
453 "items",
454 &TypeRef::Optional(Box::new(TypeRef::Vec(Box::new(TypeRef::Named("Item".into()))))),
455 false,
456 false,
457 &AHashSet::new(),
458 );
459 assert_eq!(
460 result,
461 "items: val.items.map(|v| v.into_iter().map(Into::into).collect())"
462 );
463 }
464
465 #[test]
466 fn test_field_conversion_to_core_option_map_named_applies_per_value_into() {
467 let result = field_conversion_to_core(
470 "patterns",
471 &TypeRef::Map(
472 Box::new(TypeRef::String),
473 Box::new(TypeRef::Named("ExtractionPattern".into())),
474 ),
475 true,
476 );
477 assert!(
478 result.contains("m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()"),
479 "expected per-value v.into() in optional Map<Named> conversion, got: {result}"
480 );
481 assert_eq!(
482 result,
483 "patterns: val.patterns.map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())"
484 );
485 }
486
487 #[test]
488 fn test_gen_optionalized_field_to_core_ir_optional_map_named_preserves_option() {
489 use super::binding_to_core::gen_optionalized_field_to_core;
492 let config = ConversionConfig::default();
493 let result = gen_optionalized_field_to_core(
494 "patterns",
495 &TypeRef::Map(
496 Box::new(TypeRef::String),
497 Box::new(TypeRef::Named("ExtractionPattern".into())),
498 ),
499 &config,
500 true,
501 );
502 assert!(
503 result.contains("m.into_iter().map(|(k, v)| (k, v.into())).collect()"),
504 "expected per-value v.into() in ir-optional Map<Named> conversion, got: {result}"
505 );
506 assert_eq!(
507 result,
508 "patterns: val.patterns.map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect())"
509 );
510 }
511
512 #[test]
513 fn test_optionalized_defaultable_struct_uses_core_default_as_base() {
514 let mut typ = simple_type();
515 typ.has_default = true;
516 typ.fields = vec![
517 FieldDef {
518 name: "language".into(),
519 ty: TypeRef::String,
520 optional: false,
521 default: None,
522 doc: String::new(),
523 sanitized: false,
524 is_boxed: false,
525 type_rust_path: None,
526 cfg: None,
527 typed_default: None,
528 core_wrapper: CoreWrapper::Cow,
529 vec_inner_core_wrapper: CoreWrapper::None,
530 newtype_wrapper: None,
531 serde_rename: None,
532 serde_flatten: false,
533 },
534 FieldDef {
535 name: "structure".into(),
536 ty: TypeRef::Primitive(PrimitiveType::Bool),
537 optional: false,
538 default: None,
539 doc: String::new(),
540 sanitized: false,
541 is_boxed: false,
542 type_rust_path: None,
543 cfg: None,
544 typed_default: None,
545 core_wrapper: CoreWrapper::None,
546 vec_inner_core_wrapper: CoreWrapper::None,
547 newtype_wrapper: None,
548 serde_rename: None,
549 serde_flatten: false,
550 },
551 ];
552 let config = ConversionConfig {
553 type_name_prefix: "Js",
554 optionalize_defaults: true,
555 ..ConversionConfig::default()
556 };
557
558 let result = gen_from_binding_to_core_cfg(&typ, "my_crate", &config);
559
560 assert!(result.contains("let mut __result = my_crate::Config::default();"));
561 assert!(result.contains("if let Some(__v) = val.language { __result.language = __v.into(); }"));
562 assert!(result.contains("if let Some(__v) = val.structure { __result.structure = __v; }"));
563 assert!(!result.contains("unwrap_or_default()"));
564 }
565
566 fn arc_field_type(field: FieldDef) -> TypeDef {
567 TypeDef {
568 name: "State".to_string(),
569 rust_path: "my_crate::State".to_string(),
570 original_rust_path: String::new(),
571 fields: vec![field],
572 methods: vec![],
573 is_opaque: false,
574 is_clone: true,
575 is_copy: false,
576 is_trait: false,
577 has_default: false,
578 has_stripped_cfg_fields: false,
579 is_return_type: false,
580 serde_rename_all: None,
581 has_serde: false,
582 super_traits: vec![],
583 doc: String::new(),
584 cfg: None,
585 }
586 }
587
588 fn arc_field(name: &str, ty: TypeRef, optional: bool) -> FieldDef {
589 FieldDef {
590 name: name.into(),
591 ty,
592 optional,
593 default: None,
594 doc: String::new(),
595 sanitized: false,
596 is_boxed: false,
597 type_rust_path: None,
598 cfg: None,
599 typed_default: None,
600 core_wrapper: CoreWrapper::Arc,
601 vec_inner_core_wrapper: CoreWrapper::None,
602 newtype_wrapper: None,
603 serde_rename: None,
604 serde_flatten: false,
605 }
606 }
607
608 #[test]
612 fn test_arc_json_option_field_no_double_chain() {
613 let typ = arc_field_type(arc_field("registered_spec", TypeRef::Json, true));
614 let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
615 assert!(
616 result.contains("val.registered_spec.as_ref().map(ToString::to_string)"),
617 "expected as_ref().map(ToString::to_string) for Option<Arc<Value>>, got: {result}"
618 );
619 assert!(
620 !result.contains("map(ToString::to_string).map("),
621 "must not chain a second map() on top of ToString::to_string, got: {result}"
622 );
623 }
624
625 #[test]
627 fn test_arc_json_non_optional_field() {
628 let typ = arc_field_type(arc_field("spec", TypeRef::Json, false));
629 let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
630 assert!(
631 result.contains("(*val.spec).clone().to_string()"),
632 "expected (*val.spec).clone().to_string() for Arc<Value>, got: {result}"
633 );
634 }
635
636 #[test]
639 fn test_arc_string_option_field_passthrough() {
640 let typ = arc_field_type(arc_field("label", TypeRef::String, true));
641 let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
642 assert!(
643 result.contains("val.label.map(|v| (*v).clone().into())"),
644 "expected .map(|v| (*v).clone().into()) for Option<Arc<String>>, got: {result}"
645 );
646 }
647}