Skip to main content

alef_codegen/conversions/
mod.rs

1mod binding_to_core;
2mod core_to_binding;
3mod enums;
4mod helpers;
5
6use ahash::AHashSet;
7
8/// Backend-specific configuration for From/field conversion generation.
9/// Enables shared code to handle all backend differences via parameters.
10#[derive(Default, Clone)]
11pub struct ConversionConfig<'a> {
12    /// Prefix for binding type names ("Js" for NAPI/WASM, "" for others).
13    pub type_name_prefix: &'a str,
14    /// U64/Usize/Isize need `as i64` casts (NAPI, PHP — JS/PHP lack native u64).
15    pub cast_large_ints_to_i64: bool,
16    /// Enum names mapped to String in the binding layer (PHP only).
17    /// Named fields referencing these use `format!("{:?}")` in core→binding.
18    pub enum_string_names: Option<&'a AHashSet<String>>,
19    /// Map types use JsValue in the binding layer (WASM only).
20    /// When true, Map fields use `serde_wasm_bindgen` for conversion instead of
21    /// iterator-based collect patterns (JsValue is not iterable).
22    pub map_uses_jsvalue: bool,
23    /// When true, f32 is mapped to f64 (NAPI only — JS has no f32).
24    pub cast_f32_to_f64: bool,
25    /// When true, non-optional fields on defaultable types are wrapped in Option<T>
26    /// in the binding struct and need `.unwrap_or_default()` in binding→core From.
27    /// Used by NAPI to make JS-facing structs fully optional.
28    pub optionalize_defaults: bool,
29    /// When true, Json (serde_json::Value) fields are mapped to String in the binding layer.
30    /// Core→binding uses `.to_string()`, binding→core uses `Default::default()` (lossy).
31    /// Used by PHP where serde_json::Value can't cross the extension boundary.
32    pub json_to_string: bool,
33    /// When true, add synthetic metadata field conversion for ConversionResult.
34    /// Only NAPI backend sets this (it adds metadata field to the struct).
35    pub include_cfg_metadata: bool,
36}
37
38// Re-export all public items so callers continue to use `conversions::foo`.
39pub use binding_to_core::{
40    field_conversion_to_core, field_conversion_to_core_cfg, gen_from_binding_to_core, gen_from_binding_to_core_cfg,
41};
42pub use core_to_binding::{
43    field_conversion_from_core, field_conversion_from_core_cfg, gen_from_core_to_binding, gen_from_core_to_binding_cfg,
44};
45pub use enums::{
46    gen_enum_from_binding_to_core, gen_enum_from_binding_to_core_cfg, gen_enum_from_core_to_binding,
47    gen_enum_from_core_to_binding_cfg,
48};
49pub use helpers::{
50    binding_to_core_match_arm, can_generate_conversion, can_generate_enum_conversion,
51    can_generate_enum_conversion_from_core, convertible_types, core_enum_path, core_to_binding_convertible_types,
52    core_to_binding_match_arm, core_type_path, has_sanitized_fields, is_tuple_variant,
53};
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use alef_core::ir::*;
59
60    fn simple_type() -> TypeDef {
61        TypeDef {
62            name: "Config".to_string(),
63            rust_path: "my_crate::Config".to_string(),
64            fields: vec![
65                FieldDef {
66                    name: "name".into(),
67                    ty: TypeRef::String,
68                    optional: false,
69                    default: None,
70                    doc: String::new(),
71                    sanitized: false,
72                    is_boxed: false,
73                    type_rust_path: None,
74                    cfg: None,
75                    typed_default: None,
76                },
77                FieldDef {
78                    name: "timeout".into(),
79                    ty: TypeRef::Primitive(PrimitiveType::U64),
80                    optional: true,
81                    default: None,
82                    doc: String::new(),
83                    sanitized: false,
84                    is_boxed: false,
85                    type_rust_path: None,
86                    cfg: None,
87                    typed_default: None,
88                },
89                FieldDef {
90                    name: "backend".into(),
91                    ty: TypeRef::Named("Backend".into()),
92                    optional: true,
93                    default: None,
94                    doc: String::new(),
95                    sanitized: false,
96                    is_boxed: false,
97                    type_rust_path: None,
98                    cfg: None,
99                    typed_default: None,
100                },
101            ],
102            methods: vec![],
103            is_opaque: false,
104            is_clone: true,
105            is_trait: false,
106            has_default: false,
107            has_stripped_cfg_fields: false,
108            is_return_type: false,
109            doc: String::new(),
110            cfg: None,
111        }
112    }
113
114    fn simple_enum() -> EnumDef {
115        EnumDef {
116            name: "Backend".to_string(),
117            rust_path: "my_crate::Backend".to_string(),
118            variants: vec![
119                EnumVariant {
120                    name: "Cpu".into(),
121                    fields: vec![],
122                    doc: String::new(),
123                    is_default: false,
124                },
125                EnumVariant {
126                    name: "Gpu".into(),
127                    fields: vec![],
128                    doc: String::new(),
129                    is_default: false,
130                },
131            ],
132            doc: String::new(),
133            cfg: None,
134        }
135    }
136
137    #[test]
138    fn test_from_binding_to_core() {
139        let typ = simple_type();
140        let result = gen_from_binding_to_core(&typ, "my_crate");
141        assert!(result.contains("impl From<Config> for my_crate::Config"));
142        assert!(result.contains("name: val.name"));
143        assert!(result.contains("timeout: val.timeout"));
144        assert!(result.contains("backend: val.backend.map(Into::into)"));
145    }
146
147    #[test]
148    fn test_from_core_to_binding() {
149        let typ = simple_type();
150        let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
151        assert!(result.contains("impl From<my_crate::Config> for Config"));
152    }
153
154    #[test]
155    fn test_enum_from_binding_to_core() {
156        let enum_def = simple_enum();
157        let result = gen_enum_from_binding_to_core(&enum_def, "my_crate");
158        assert!(result.contains("impl From<Backend> for my_crate::Backend"));
159        assert!(result.contains("Backend::Cpu => Self::Cpu"));
160        assert!(result.contains("Backend::Gpu => Self::Gpu"));
161    }
162
163    #[test]
164    fn test_enum_from_core_to_binding() {
165        let enum_def = simple_enum();
166        let result = gen_enum_from_core_to_binding(&enum_def, "my_crate");
167        assert!(result.contains("impl From<my_crate::Backend> for Backend"));
168        assert!(result.contains("my_crate::Backend::Cpu => Self::Cpu"));
169        assert!(result.contains("my_crate::Backend::Gpu => Self::Gpu"));
170    }
171}