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 include_cfg_metadata: bool,
36 pub option_duration_on_defaults: bool,
42 pub binding_enums_have_data: bool,
45 pub exclude_types: &'a [String],
49 pub vec_named_to_string: bool,
54 pub map_as_string: bool,
58 pub opaque_types: Option<&'a AHashSet<String>>,
63 pub source_crate_remaps: &'a [(&'a str, &'a str)],
71 pub binding_field_renames: Option<&'a std::collections::HashMap<String, String>>,
79}
80
81impl<'a> ConversionConfig<'a> {
82 pub fn binding_field_name<'b>(&self, type_name: &str, field_name: &'b str) -> &'b str
87 where
88 'a: 'b,
89 {
90 let _ = type_name;
95 field_name
96 }
97
98 pub fn binding_field_name_owned(&self, type_name: &str, field_name: &str) -> String {
101 if let Some(map) = self.binding_field_renames {
102 let key = format!("{type_name}.{field_name}");
103 if let Some(renamed) = map.get(&key) {
104 return renamed.clone();
105 }
106 }
107 field_name.to_string()
108 }
109}
110
111pub use binding_to_core::{
113 field_conversion_to_core, field_conversion_to_core_cfg, gen_from_binding_to_core, gen_from_binding_to_core_cfg,
114};
115pub use core_to_binding::{
116 field_conversion_from_core, field_conversion_from_core_cfg, gen_from_core_to_binding, gen_from_core_to_binding_cfg,
117};
118pub use enums::{
119 gen_enum_from_binding_to_core, gen_enum_from_binding_to_core_cfg, gen_enum_from_core_to_binding,
120 gen_enum_from_core_to_binding_cfg,
121};
122pub use helpers::{
123 apply_crate_remaps, binding_to_core_match_arm, build_type_path_map, can_generate_conversion,
124 can_generate_enum_conversion, can_generate_enum_conversion_from_core, convertible_types, core_enum_path,
125 core_enum_path_remapped, core_to_binding_convertible_types, core_to_binding_match_arm, core_type_path,
126 core_type_path_remapped, field_references_excluded_type, has_sanitized_fields, input_type_names, is_tuple_variant,
127 resolve_named_path,
128};
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use alef_core::ir::*;
134
135 fn simple_type() -> TypeDef {
136 TypeDef {
137 name: "Config".to_string(),
138 rust_path: "my_crate::Config".to_string(),
139 original_rust_path: String::new(),
140 fields: vec![
141 FieldDef {
142 name: "name".into(),
143 ty: TypeRef::String,
144 optional: false,
145 default: None,
146 doc: String::new(),
147 sanitized: false,
148 is_boxed: false,
149 type_rust_path: None,
150 cfg: None,
151 typed_default: None,
152 core_wrapper: CoreWrapper::None,
153 vec_inner_core_wrapper: CoreWrapper::None,
154 newtype_wrapper: None,
155 },
156 FieldDef {
157 name: "timeout".into(),
158 ty: TypeRef::Primitive(PrimitiveType::U64),
159 optional: true,
160 default: None,
161 doc: String::new(),
162 sanitized: false,
163 is_boxed: false,
164 type_rust_path: None,
165 cfg: None,
166 typed_default: None,
167 core_wrapper: CoreWrapper::None,
168 vec_inner_core_wrapper: CoreWrapper::None,
169 newtype_wrapper: None,
170 },
171 FieldDef {
172 name: "backend".into(),
173 ty: TypeRef::Named("Backend".into()),
174 optional: true,
175 default: None,
176 doc: String::new(),
177 sanitized: false,
178 is_boxed: false,
179 type_rust_path: None,
180 cfg: None,
181 typed_default: None,
182 core_wrapper: CoreWrapper::None,
183 vec_inner_core_wrapper: CoreWrapper::None,
184 newtype_wrapper: None,
185 },
186 ],
187 methods: vec![],
188 is_opaque: false,
189 is_clone: true,
190 is_copy: false,
191 is_trait: false,
192 has_default: false,
193 has_stripped_cfg_fields: false,
194 is_return_type: false,
195 serde_rename_all: None,
196 has_serde: false,
197 super_traits: vec![],
198 doc: String::new(),
199 cfg: None,
200 }
201 }
202
203 fn simple_enum() -> EnumDef {
204 EnumDef {
205 name: "Backend".to_string(),
206 rust_path: "my_crate::Backend".to_string(),
207 original_rust_path: String::new(),
208 variants: vec![
209 EnumVariant {
210 name: "Cpu".into(),
211 fields: vec![],
212 is_tuple: false,
213 doc: String::new(),
214 is_default: false,
215 serde_rename: None,
216 },
217 EnumVariant {
218 name: "Gpu".into(),
219 fields: vec![],
220 is_tuple: false,
221 doc: String::new(),
222 is_default: false,
223 serde_rename: None,
224 },
225 ],
226 doc: String::new(),
227 cfg: None,
228 is_copy: false,
229 has_serde: false,
230 serde_tag: None,
231 serde_rename_all: None,
232 }
233 }
234
235 #[test]
236 fn test_from_binding_to_core() {
237 let typ = simple_type();
238 let result = gen_from_binding_to_core(&typ, "my_crate");
239 assert!(result.contains("impl From<Config> for my_crate::Config"));
240 assert!(result.contains("name: val.name"));
241 assert!(result.contains("timeout: val.timeout"));
242 assert!(result.contains("backend: val.backend.map(Into::into)"));
243 }
244
245 #[test]
246 fn test_from_core_to_binding() {
247 let typ = simple_type();
248 let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
249 assert!(result.contains("impl From<my_crate::Config> for Config"));
250 }
251
252 #[test]
253 fn test_enum_from_binding_to_core() {
254 let enum_def = simple_enum();
255 let result = gen_enum_from_binding_to_core(&enum_def, "my_crate");
256 assert!(result.contains("impl From<Backend> for my_crate::Backend"));
257 assert!(result.contains("Backend::Cpu => Self::Cpu"));
258 assert!(result.contains("Backend::Gpu => Self::Gpu"));
259 }
260
261 #[test]
262 fn test_enum_from_core_to_binding() {
263 let enum_def = simple_enum();
264 let result = gen_enum_from_core_to_binding(&enum_def, "my_crate");
265 assert!(result.contains("impl From<my_crate::Backend> for Backend"));
266 assert!(result.contains("my_crate::Backend::Cpu => Self::Cpu"));
267 assert!(result.contains("my_crate::Backend::Gpu => Self::Gpu"));
268 }
269
270 #[test]
271 fn test_from_binding_to_core_with_cfg_gated_field() {
272 let mut typ = simple_type();
274 typ.has_stripped_cfg_fields = true;
275 typ.fields.push(FieldDef {
276 name: "layout".into(),
277 ty: TypeRef::String,
278 optional: false,
279 default: None,
280 doc: String::new(),
281 sanitized: false,
282 is_boxed: false,
283 type_rust_path: None,
284 cfg: Some("feature = \"layout-detection\"".into()),
285 typed_default: None,
286 core_wrapper: CoreWrapper::None,
287 vec_inner_core_wrapper: CoreWrapper::None,
288 newtype_wrapper: None,
289 });
290
291 let result = gen_from_binding_to_core(&typ, "my_crate");
292
293 assert!(result.contains("impl From<Config> for my_crate::Config"));
295 assert!(result.contains("name: val.name"));
297 assert!(result.contains("timeout: val.timeout"));
298 assert!(!result.contains("layout: val.layout"));
300 assert!(result.contains("..Default::default()"));
302 }
303
304 #[test]
305 fn test_from_core_to_binding_with_cfg_gated_field() {
306 let mut typ = simple_type();
308 typ.fields.push(FieldDef {
309 name: "layout".into(),
310 ty: TypeRef::String,
311 optional: false,
312 default: None,
313 doc: String::new(),
314 sanitized: false,
315 is_boxed: false,
316 type_rust_path: None,
317 cfg: Some("feature = \"layout-detection\"".into()),
318 typed_default: None,
319 core_wrapper: CoreWrapper::None,
320 vec_inner_core_wrapper: CoreWrapper::None,
321 newtype_wrapper: None,
322 });
323
324 let result = gen_from_core_to_binding(&typ, "my_crate", &AHashSet::new());
325
326 assert!(result.contains("impl From<my_crate::Config> for Config"));
328 assert!(result.contains("name: val.name"));
330 assert!(!result.contains("layout:"));
332 }
333
334 #[test]
335 fn test_field_conversion_from_core_map_named_non_optional() {
336 let result = field_conversion_from_core(
338 "tags",
339 &TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::Named("Tag".into()))),
340 false,
341 false,
342 &AHashSet::new(),
343 );
344 assert_eq!(
345 result,
346 "tags: val.tags.into_iter().map(|(k, v)| (k, v.into())).collect()"
347 );
348 }
349
350 #[test]
351 fn test_field_conversion_from_core_option_map_named() {
352 let result = field_conversion_from_core(
354 "tags",
355 &TypeRef::Optional(Box::new(TypeRef::Map(
356 Box::new(TypeRef::String),
357 Box::new(TypeRef::Named("Tag".into())),
358 ))),
359 false,
360 false,
361 &AHashSet::new(),
362 );
363 assert_eq!(
364 result,
365 "tags: val.tags.map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect())"
366 );
367 }
368
369 #[test]
370 fn test_field_conversion_from_core_vec_named_non_optional() {
371 let result = field_conversion_from_core(
373 "items",
374 &TypeRef::Vec(Box::new(TypeRef::Named("Item".into()))),
375 false,
376 false,
377 &AHashSet::new(),
378 );
379 assert_eq!(result, "items: val.items.into_iter().map(Into::into).collect()");
380 }
381
382 #[test]
383 fn test_field_conversion_from_core_option_vec_named() {
384 let result = field_conversion_from_core(
386 "items",
387 &TypeRef::Optional(Box::new(TypeRef::Vec(Box::new(TypeRef::Named("Item".into()))))),
388 false,
389 false,
390 &AHashSet::new(),
391 );
392 assert_eq!(
393 result,
394 "items: val.items.map(|v| v.into_iter().map(Into::into).collect())"
395 );
396 }
397
398 #[test]
399 fn test_field_conversion_to_core_option_map_named_applies_per_value_into() {
400 let result = field_conversion_to_core(
403 "patterns",
404 &TypeRef::Map(
405 Box::new(TypeRef::String),
406 Box::new(TypeRef::Named("ExtractionPattern".into())),
407 ),
408 true,
409 );
410 assert!(
411 result.contains("m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()"),
412 "expected per-value v.into() in optional Map<Named> conversion, got: {result}"
413 );
414 assert_eq!(
415 result,
416 "patterns: val.patterns.map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())"
417 );
418 }
419
420 #[test]
421 fn test_gen_optionalized_field_to_core_ir_optional_map_named_preserves_option() {
422 use super::binding_to_core::gen_optionalized_field_to_core;
425 let config = ConversionConfig::default();
426 let result = gen_optionalized_field_to_core(
427 "patterns",
428 &TypeRef::Map(
429 Box::new(TypeRef::String),
430 Box::new(TypeRef::Named("ExtractionPattern".into())),
431 ),
432 &config,
433 true,
434 );
435 assert!(
436 result.contains("m.into_iter().map(|(k, v)| (k, v.into())).collect()"),
437 "expected per-value v.into() in ir-optional Map<Named> conversion, got: {result}"
438 );
439 assert_eq!(
440 result,
441 "patterns: val.patterns.map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect())"
442 );
443 }
444}