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