use super::MagnusBackend;
use crate::core::backend::Backend;
use crate::core::config::{NewAlefConfig, ResolvedCrateConfig};
use crate::core::ir::{ApiSurface, DefaultValue, EnumDef, EnumVariant, FieldDef, TypeDef, TypeRef};
fn magnus_config() -> ResolvedCrateConfig {
let cfg: NewAlefConfig = toml::from_str(
"[workspace]\nlanguages = [\"ruby\"]\n[[crates]]\nname = \"test-lib\"\nsources = [\"src/lib.rs\"]\n\
[crates.ruby]\ngem_name = \"test_lib\"\n",
)
.expect("fixture alef.toml parses");
cfg.resolve().expect("fixture alef.toml resolves").remove(0)
}
fn derived_default_field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
typed_default: Some(DefaultValue::Empty),
..Default::default()
}
}
fn undefaultable_untagged_enum() -> EnumDef {
EnumDef {
name: "EmbeddingInput".to_string(),
rust_path: "test_lib::EmbeddingInput".to_string(),
serde_untagged: true,
has_default: false,
variants: vec![
EnumVariant {
name: "Single".to_string(),
is_tuple: true,
fields: vec![FieldDef {
name: "_0".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
},
EnumVariant {
name: "Multiple".to_string(),
is_tuple: true,
fields: vec![FieldDef {
name: "_0".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::String)),
..Default::default()
}],
..Default::default()
},
],
..Default::default()
}
}
fn defaultable_unit_enum() -> EnumDef {
EnumDef {
name: "EmbeddingFormat".to_string(),
rust_path: "test_lib::EmbeddingFormat".to_string(),
has_default: false,
variants: vec![
EnumVariant {
name: "Float".to_string(),
..Default::default()
},
EnumVariant {
name: "Base64".to_string(),
..Default::default()
},
],
..Default::default()
}
}
fn embedding_request_api() -> ApiSurface {
ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
enums: vec![undefaultable_untagged_enum(), defaultable_unit_enum()],
types: vec![TypeDef {
name: "EmbeddingRequest".to_string(),
rust_path: "test_lib::EmbeddingRequest".to_string(),
has_default: true,
has_serde: true,
is_clone: true,
fields: vec![
derived_default_field("model", TypeRef::String),
derived_default_field("input", TypeRef::Named("EmbeddingInput".to_string())),
derived_default_field("encoding_format", TypeRef::Named("EmbeddingFormat".to_string())),
],
..Default::default()
}],
..Default::default()
}
}
fn generated_lib_rs() -> String {
let files = MagnusBackend
.generate_bindings(&embedding_request_api(), &magnus_config())
.expect("magnus bindings generate");
files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("lib.rs"))
.expect("generate_bindings must emit lib.rs")
.content
.clone()
}
fn field_initializer<'a>(lib_rs: &'a str, field: &str) -> &'a str {
let needle = format!("match kwargs.get(ruby.to_symbol(\"{field}\"))");
let start = lib_rs
.find(&needle)
.unwrap_or_else(|| panic!("the kwargs constructor must extract `{field}`:\n{lib_rs}"));
let end = lib_rs[start..]
.find("},\n")
.map(|i| start + i + 1)
.unwrap_or_else(|| panic!("`{field}`'s match arm must close:\n{}", &lib_rs[start..]));
&lib_rs[start..end]
}
#[test]
fn generated_untagged_enum_has_no_default_impl() {
let lib_rs = generated_lib_rs();
assert!(
lib_rs.contains("pub enum EmbeddingInput {"),
"the generated crate must re-declare the untagged enum:\n{lib_rs}"
);
assert!(
!lib_rs.contains("impl Default for EmbeddingInput"),
"fixture premise broken: the generated crate now has a Default impl for EmbeddingInput, \
so `Default::default()` on it would compile and this module tests nothing:\n{lib_rs}"
);
assert!(
lib_rs.contains("impl Default for EmbeddingFormat"),
"fixture premise broken: the unit-enum control must have a Default impl, or the positive \
control below proves nothing:\n{lib_rs}"
);
}
#[test]
fn undefaultable_untagged_enum_field_raises_instead_of_defaulting() {
let lib_rs = generated_lib_rs();
let input = field_initializer(&lib_rs, "input");
assert!(
!input.contains("Default::default()"),
"`input`'s type has no generated Default impl, so this fallback is an E0277:\n{input}"
);
assert!(
input.contains("exception_arg_error()") && input.contains("missing required field: input"),
"an absent keyword for a genuinely required field must raise a Ruby ArgumentError naming \
it:\n{input}"
);
}
#[test]
fn string_field_still_defaults() {
let lib_rs = generated_lib_rs();
let model = field_initializer(&lib_rs, "model");
assert!(
model.contains("None => Default::default()"),
"a field whose Default is structural must keep its fallback:\n{model}"
);
assert!(
!model.contains("missing required field"),
"`model` must not become a required keyword:\n{model}"
);
}
#[test]
fn defaultable_unit_enum_field_still_defaults() {
let lib_rs = generated_lib_rs();
let encoding_format = field_initializer(&lib_rs, "encoding_format");
assert!(
encoding_format.contains("None => Default::default()"),
"a Named field whose generated type has a Default impl must keep its fallback:\n{encoding_format}"
);
assert!(
!encoding_format.contains("missing required field"),
"`encoding_format` must not become a required keyword:\n{encoding_format}"
);
}