use super::*;
use crate::core::ir::{EnumDef, EnumVariant, FieldDef, PrimitiveType, TypeDef, TypeRef};
use ahash::{AHashMap, AHashSet};
fn empty_api() -> ApiSurface {
ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
types: vec![],
functions: vec![],
enums: vec![],
errors: vec![],
excluded_type_paths: std::collections::BTreeMap::new(),
excluded_trait_names: std::collections::HashSet::new(),
services: vec![],
handler_contracts: vec![],
unsupported_public_items: Vec::new(),
}
}
fn tuple_variant(name: &str, ty: TypeRef) -> EnumVariant {
EnumVariant {
name: name.to_string(),
fields: vec![FieldDef {
name: "_0".to_string(),
ty,
..Default::default()
}],
is_tuple: true,
..Default::default()
}
}
fn plan_for(enum_def: &EnumDef, api: &ApiSurface) -> String {
let exclude_types = AHashSet::default();
let opaque_types = AHashSet::default();
build_untagged_enum_ts_plans(&[enum_def], api, &exclude_types, &opaque_types, "Alef").custom_section
}
#[test]
fn embedding_input_maps_to_string_or_string_array() {
let enum_def = EnumDef {
name: "EmbeddingInput".to_string(),
rust_path: "test_lib::EmbeddingInput".to_string(),
variants: vec![
tuple_variant("Single", TypeRef::String),
tuple_variant("Multiple", TypeRef::Vec(Box::new(TypeRef::String))),
],
serde_untagged: true,
..Default::default()
};
let api = empty_api();
let all_plans =
build_untagged_enum_ts_plans(&[&enum_def], &api, &AHashSet::default(), &AHashSet::default(), "Alef");
assert!(
all_plans
.custom_section
.contains("export type AlefEmbeddingInput = string | string[];"),
"actual:\n{}",
all_plans.custom_section
);
let plan = all_plans.plans.get("EmbeddingInput").expect("plan for EmbeddingInput");
assert!(
plan.extern_type_declaration
.contains(r#"typescript_type = "AlefEmbeddingInput""#)
);
assert!(
plan.extern_type_declaration
.contains("pub type AlefEmbeddingInputValue;")
);
}
#[test]
fn moderation_input_maps_to_string_or_string_array() {
let enum_def = EnumDef {
name: "ModerationInput".to_string(),
rust_path: "test_lib::ModerationInput".to_string(),
variants: vec![
tuple_variant("Single", TypeRef::String),
tuple_variant("Multiple", TypeRef::Vec(Box::new(TypeRef::String))),
],
serde_untagged: true,
..Default::default()
};
let plan = plan_for(&enum_def, &empty_api());
assert!(
plan.contains("export type AlefModerationInput = string | string[];"),
"actual:\n{}",
plan
);
}
#[test]
fn rerank_document_maps_newtype_and_struct_variant() {
let enum_def = EnumDef {
name: "RerankDocument".to_string(),
rust_path: "test_lib::RerankDocument".to_string(),
variants: vec![
tuple_variant("Text", TypeRef::String),
EnumVariant {
name: "Object".to_string(),
fields: vec![FieldDef {
name: "text".to_string(),
ty: TypeRef::String,
..Default::default()
}],
is_tuple: false,
..Default::default()
},
],
serde_untagged: true,
..Default::default()
};
let plan = plan_for(&enum_def, &empty_api());
assert!(
plan.contains("export type AlefRerankDocument = string | { text: string; };"),
"actual:\n{}",
plan
);
}
fn content_part_type() -> TypeDef {
TypeDef {
name: "ContentPart".to_string(),
rust_path: "test_lib::ContentPart".to_string(),
fields: vec![
FieldDef {
name: "text".to_string(),
ty: TypeRef::String,
..Default::default()
},
FieldDef {
name: "kind".to_string(),
ty: TypeRef::String,
..Default::default()
},
],
..Default::default()
}
}
#[test]
fn user_content_maps_to_string_or_content_part_array() {
let enum_def = EnumDef {
name: "UserContent".to_string(),
rust_path: "test_lib::UserContent".to_string(),
variants: vec![
tuple_variant("Text", TypeRef::String),
tuple_variant(
"Parts",
TypeRef::Vec(Box::new(TypeRef::Named("ContentPart".to_string()))),
),
],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![content_part_type()];
let plan = plan_for(&enum_def, &api);
assert!(
plan.contains("export type AlefUserContent = string | AlefContentPartWire[];"),
"actual:\n{}",
plan
);
assert!(
plan.contains("export interface AlefContentPartWire {\n text: string;\n kind: string;\n}"),
"actual:\n{}",
plan
);
}
#[test]
fn assistant_content_maps_to_string_or_assistant_part_array() {
let enum_def = EnumDef {
name: "AssistantContent".to_string(),
rust_path: "test_lib::AssistantContent".to_string(),
variants: vec![
tuple_variant("Text", TypeRef::String),
tuple_variant(
"Parts",
TypeRef::Vec(Box::new(TypeRef::Named("AssistantPart".to_string()))),
),
],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![TypeDef {
name: "AssistantPart".to_string(),
rust_path: "test_lib::AssistantPart".to_string(),
fields: vec![FieldDef {
name: "text".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
}];
let plan = plan_for(&enum_def, &api);
assert!(
plan.contains("export type AlefAssistantContent = string | AlefAssistantPartWire[];"),
"actual:\n{}",
plan
);
}
#[test]
fn tool_choice_maps_unit_enum_and_struct_newtype_variants() {
let mode_enum = EnumDef {
name: "ToolChoiceMode".to_string(),
rust_path: "test_lib::ToolChoiceMode".to_string(),
variants: vec![
EnumVariant {
name: "Auto".to_string(),
..Default::default()
},
EnumVariant {
name: "Required".to_string(),
..Default::default()
},
EnumVariant {
name: "None".to_string(),
..Default::default()
},
],
serde_rename_all: Some("snake_case".to_string()),
is_copy: true,
..Default::default()
};
let enum_def = EnumDef {
name: "ToolChoice".to_string(),
rust_path: "test_lib::ToolChoice".to_string(),
variants: vec![
tuple_variant("Mode", TypeRef::Named("ToolChoiceMode".to_string())),
tuple_variant("Specific", TypeRef::Named("SpecificToolChoice".to_string())),
],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.enums = vec![mode_enum];
api.types = vec![TypeDef {
name: "SpecificToolChoice".to_string(),
rust_path: "test_lib::SpecificToolChoice".to_string(),
fields: vec![FieldDef {
name: "name".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
}];
let plan = plan_for(&enum_def, &api);
assert!(
plan.contains("export type AlefToolChoice = AlefToolChoiceModeWire | AlefSpecificToolChoiceWire;"),
"actual:\n{}",
plan
);
assert!(
plan.contains(r#"export type AlefToolChoiceModeWire = "auto" | "required" | "none";"#),
"actual:\n{}",
plan
);
assert!(
plan.contains("export interface AlefSpecificToolChoiceWire {\n name: string;\n}"),
"actual:\n{}",
plan
);
}
#[test]
fn primitive_types_map_to_expected_ts() {
let cases: &[(TypeRef, &str)] = &[
(TypeRef::Primitive(PrimitiveType::Bool), "boolean"),
(TypeRef::Primitive(PrimitiveType::U32), "number"),
(TypeRef::Primitive(PrimitiveType::F64), "number"),
(TypeRef::Primitive(PrimitiveType::U64), "bigint"),
(TypeRef::Primitive(PrimitiveType::I64), "bigint"),
(TypeRef::String, "string"),
(TypeRef::Char, "string"),
(TypeRef::Path, "string"),
(TypeRef::Bytes, "Uint8Array"),
(TypeRef::Unit, "null"),
(TypeRef::Json, "any"),
(TypeRef::Duration, "number"),
];
let api = empty_api();
for (ty, expected) in cases {
let mut ctx = TsMapContext {
api: &api,
exclude_types: &AHashSet::default(),
opaque_type_names: &AHashSet::default(),
prefix: "Alef",
in_progress: AHashMap::default(),
resolved_names: ahash::AHashMap::default(),
decls: Vec::new(),
};
assert_eq!(ctx.map_type(ty), *expected, "mapping {ty:?}");
}
}
#[test]
fn vec_maps_to_element_array() {
let api = empty_api();
let mut ctx = TsMapContext {
api: &api,
exclude_types: &AHashSet::default(),
opaque_type_names: &AHashSet::default(),
prefix: "Alef",
in_progress: AHashMap::default(),
resolved_names: ahash::AHashMap::default(),
decls: Vec::new(),
};
let ty = TypeRef::Vec(Box::new(TypeRef::String));
assert_eq!(ctx.map_type(&ty), "string[]");
}
#[test]
fn vec_of_optional_wraps_union_before_appending_array_suffix() {
let api = empty_api();
let mut ctx = TsMapContext {
api: &api,
exclude_types: &AHashSet::default(),
opaque_type_names: &AHashSet::default(),
prefix: "Alef",
in_progress: AHashMap::default(),
resolved_names: ahash::AHashMap::default(),
decls: Vec::new(),
};
let ty = TypeRef::Vec(Box::new(TypeRef::Optional(Box::new(TypeRef::String))));
assert_eq!(ctx.map_type(&ty), "(string | undefined)[]");
}
#[test]
fn nested_vec_of_optional_parenthesizes_at_the_right_level() {
let api = empty_api();
let mut ctx = TsMapContext {
api: &api,
exclude_types: &AHashSet::default(),
opaque_type_names: &AHashSet::default(),
prefix: "Alef",
in_progress: AHashMap::default(),
resolved_names: ahash::AHashMap::default(),
decls: Vec::new(),
};
let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::Optional(Box::new(
TypeRef::String,
))))));
assert_eq!(ctx.map_type(&ty), "(string | undefined)[][]");
}
#[test]
fn optional_appends_undefined() {
let api = empty_api();
let mut ctx = TsMapContext {
api: &api,
exclude_types: &AHashSet::default(),
opaque_type_names: &AHashSet::default(),
prefix: "Alef",
in_progress: AHashMap::default(),
resolved_names: ahash::AHashMap::default(),
decls: Vec::new(),
};
let ty = TypeRef::Optional(Box::new(TypeRef::String));
assert_eq!(ctx.map_type(&ty), "string | undefined");
}
#[test]
fn string_keyed_map_becomes_record() {
let api = empty_api();
let mut ctx = TsMapContext {
api: &api,
exclude_types: &AHashSet::default(),
opaque_type_names: &AHashSet::default(),
prefix: "Alef",
in_progress: AHashMap::default(),
resolved_names: ahash::AHashMap::default(),
decls: Vec::new(),
};
let ty = TypeRef::Map(
Box::new(TypeRef::String),
Box::new(TypeRef::Primitive(PrimitiveType::U32)),
);
assert_eq!(ctx.map_type(&ty), "Record<string, number>");
}
#[test]
fn non_string_keyed_map_falls_back_to_any() {
let api = empty_api();
let mut ctx = TsMapContext {
api: &api,
exclude_types: &AHashSet::default(),
opaque_type_names: &AHashSet::default(),
prefix: "Alef",
in_progress: AHashMap::default(),
resolved_names: ahash::AHashMap::default(),
decls: Vec::new(),
};
let ty = TypeRef::Map(
Box::new(TypeRef::Primitive(PrimitiveType::U32)),
Box::new(TypeRef::String),
);
assert_eq!(ctx.map_type(&ty), "any");
}
#[test]
fn excluded_type_falls_back_to_any_for_that_variant_only() {
let enum_def = EnumDef {
name: "Mixed".to_string(),
rust_path: "test_lib::Mixed".to_string(),
variants: vec![
tuple_variant("Plain", TypeRef::String),
tuple_variant("Opaque", TypeRef::Named("SecretHandle".to_string())),
],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![TypeDef {
name: "SecretHandle".to_string(),
rust_path: "test_lib::SecretHandle".to_string(),
..Default::default()
}];
let exclude_types: AHashSet<String> = ["SecretHandle".to_string()].into_iter().collect();
let plan =
build_untagged_enum_ts_plans(&[&enum_def], &api, &exclude_types, &AHashSet::default(), "Alef").custom_section;
assert!(
plan.contains("export type AlefMixed = string | any;"),
"excluding one variant's type must not collapse the whole union to any;\nactual:\n{}",
plan
);
}
#[test]
fn opaque_type_falls_back_to_any_for_that_variant_only() {
let enum_def = EnumDef {
name: "Mixed".to_string(),
rust_path: "test_lib::Mixed".to_string(),
variants: vec![
tuple_variant("Plain", TypeRef::String),
tuple_variant("Handle", TypeRef::Named("ClientHandle".to_string())),
],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![TypeDef {
name: "ClientHandle".to_string(),
rust_path: "test_lib::ClientHandle".to_string(),
is_opaque: true,
..Default::default()
}];
let opaque_types: AHashSet<String> = ["ClientHandle".to_string()].into_iter().collect();
let plan =
build_untagged_enum_ts_plans(&[&enum_def], &api, &AHashSet::default(), &opaque_types, "Alef").custom_section;
assert!(
plan.contains("export type AlefMixed = string | any;"),
"actual:\n{}",
plan
);
}
#[test]
fn unresolvable_named_type_falls_back_to_any() {
let enum_def = EnumDef {
name: "Mixed".to_string(),
rust_path: "test_lib::Mixed".to_string(),
variants: vec![tuple_variant("Generic", TypeRef::Named("T".to_string()))],
serde_untagged: true,
..Default::default()
};
let plan = plan_for(&enum_def, &empty_api());
assert!(plan.contains("export type AlefMixed = any;"), "actual:\n{}", plan);
}
#[test]
fn self_referential_struct_terminates_and_reuses_its_own_name() {
let enum_def = EnumDef {
name: "TreeInput".to_string(),
rust_path: "test_lib::TreeInput".to_string(),
variants: vec![tuple_variant("Node", TypeRef::Named("TreeNode".to_string()))],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![TypeDef {
name: "TreeNode".to_string(),
rust_path: "test_lib::TreeNode".to_string(),
fields: vec![
FieldDef {
name: "value".to_string(),
ty: TypeRef::String,
..Default::default()
},
FieldDef {
name: "children".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("TreeNode".to_string()))),
..Default::default()
},
],
..Default::default()
}];
let plan = plan_for(&enum_def, &api);
assert!(
plan.contains("export interface AlefTreeNodeWire {\n value: string;\n children: AlefTreeNodeWire[];\n}"),
"the self-reference must resolve to the interface's own name, not re-expand or fall back to any;\nactual:\n{}",
plan
);
assert_eq!(
plan.matches("export interface AlefTreeNodeWire").count(),
1,
"actual:\n{}",
plan
);
}
#[test]
fn mutually_recursive_structs_terminate_and_dedupe() {
let enum_def = EnumDef {
name: "PairInput".to_string(),
rust_path: "test_lib::PairInput".to_string(),
variants: vec![tuple_variant("A", TypeRef::Named("NodeA".to_string()))],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![
TypeDef {
name: "NodeA".to_string(),
rust_path: "test_lib::NodeA".to_string(),
fields: vec![FieldDef {
name: "b".to_string(),
ty: TypeRef::Named("NodeB".to_string()),
..Default::default()
}],
..Default::default()
},
TypeDef {
name: "NodeB".to_string(),
rust_path: "test_lib::NodeB".to_string(),
fields: vec![FieldDef {
name: "a".to_string(),
ty: TypeRef::Named("NodeA".to_string()),
..Default::default()
}],
..Default::default()
},
];
let plan = plan_for(&enum_def, &api);
assert_eq!(
plan.matches("export interface AlefNodeAWire").count(),
1,
"actual:\n{}",
plan
);
assert_eq!(
plan.matches("export interface AlefNodeBWire").count(),
1,
"actual:\n{}",
plan
);
}
#[test]
fn optional_flag_field_appends_undefined_once() {
let enum_def = EnumDef {
name: "WithOptionalField".to_string(),
rust_path: "test_lib::WithOptionalField".to_string(),
variants: vec![tuple_variant("Data", TypeRef::Named("HasOptional".to_string()))],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![TypeDef {
name: "HasOptional".to_string(),
rust_path: "test_lib::HasOptional".to_string(),
fields: vec![FieldDef {
name: "maybe".to_string(),
ty: TypeRef::String,
optional: true,
..Default::default()
}],
..Default::default()
}];
let plan = plan_for(&enum_def, &api);
assert!(
plan.contains("export interface AlefHasOptionalWire {\n maybe: string | undefined;\n}"),
"actual:\n{}",
plan
);
}
#[test]
fn shared_struct_reference_is_emitted_once() {
let enum_def = EnumDef {
name: "SharedRef".to_string(),
rust_path: "test_lib::SharedRef".to_string(),
variants: vec![
tuple_variant("First", TypeRef::Named("Shared".to_string())),
tuple_variant("Second", TypeRef::Vec(Box::new(TypeRef::Named("Shared".to_string())))),
],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![TypeDef {
name: "Shared".to_string(),
rust_path: "test_lib::Shared".to_string(),
fields: vec![FieldDef {
name: "id".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
}];
let plan = plan_for(&enum_def, &api);
assert!(
plan.contains("export type AlefSharedRef = AlefSharedWire | AlefSharedWire[];"),
"actual:\n{}",
plan
);
assert_eq!(
plan.matches("export interface AlefSharedWire").count(),
1,
"actual:\n{}",
plan
);
}
#[test]
fn fieldless_enum_shared_by_two_unions_is_declared_once() {
let mode = EnumDef {
name: "Mode".to_string(),
rust_path: "test_lib::Mode".to_string(),
variants: vec![
EnumVariant {
name: "Fast".to_string(),
..Default::default()
},
EnumVariant {
name: "Slow".to_string(),
..Default::default()
},
],
serde_rename_all: Some("snake_case".to_string()),
is_copy: true,
..Default::default()
};
let first = EnumDef {
name: "FirstChoice".to_string(),
rust_path: "test_lib::FirstChoice".to_string(),
variants: vec![tuple_variant("Mode", TypeRef::Named("Mode".to_string()))],
serde_untagged: true,
..Default::default()
};
let second = EnumDef {
name: "SecondChoice".to_string(),
rust_path: "test_lib::SecondChoice".to_string(),
variants: vec![tuple_variant("Mode", TypeRef::Named("Mode".to_string()))],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.enums = vec![mode];
let all_plans = build_untagged_enum_ts_plans(
&[&first, &second],
&api,
&AHashSet::default(),
&AHashSet::default(),
"Alef",
);
assert_eq!(
all_plans.custom_section.matches("export type AlefModeWire").count(),
1,
"the shared alias must be declared exactly once across both unions;\nactual:\n{}",
all_plans.custom_section
);
assert!(
all_plans
.custom_section
.contains("export type AlefFirstChoice = AlefModeWire;"),
"actual:\n{}",
all_plans.custom_section
);
assert!(
all_plans
.custom_section
.contains("export type AlefSecondChoice = AlefModeWire;"),
"actual:\n{}",
all_plans.custom_section
);
}
#[test]
fn struct_shared_by_two_unions_is_referenced_by_both() {
let shared = TypeDef {
name: "Shared".to_string(),
rust_path: "test_lib::Shared".to_string(),
fields: vec![FieldDef {
name: "id".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
};
let first = EnumDef {
name: "FirstHolder".to_string(),
rust_path: "test_lib::FirstHolder".to_string(),
variants: vec![tuple_variant("Item", TypeRef::Named("Shared".to_string()))],
serde_untagged: true,
..Default::default()
};
let second = EnumDef {
name: "SecondHolder".to_string(),
rust_path: "test_lib::SecondHolder".to_string(),
variants: vec![tuple_variant("Item", TypeRef::Named("Shared".to_string()))],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.types = vec![shared];
let all_plans = build_untagged_enum_ts_plans(
&[&first, &second],
&api,
&AHashSet::default(),
&AHashSet::default(),
"Alef",
);
assert!(
all_plans
.custom_section
.contains("export type AlefFirstHolder = AlefSharedWire;"),
"actual:\n{}",
all_plans.custom_section
);
assert!(
all_plans
.custom_section
.contains("export type AlefSecondHolder = AlefSharedWire;"),
"actual:\n{}",
all_plans.custom_section
);
assert_eq!(
all_plans
.custom_section
.matches("export interface AlefSharedWire")
.count(),
1,
"actual:\n{}",
all_plans.custom_section
);
}
#[test]
fn nested_reference_to_a_sibling_top_level_union_still_declares_it() {
let nested = EnumDef {
name: "Nested".to_string(),
rust_path: "test_lib::Nested".to_string(),
variants: vec![tuple_variant("Value", TypeRef::String)],
serde_untagged: true,
..Default::default()
};
let outer = EnumDef {
name: "Outer".to_string(),
rust_path: "test_lib::Outer".to_string(),
variants: vec![tuple_variant("Inner", TypeRef::Named("Nested".to_string()))],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.enums = vec![nested.clone()];
let all_plans = build_untagged_enum_ts_plans(
&[&outer, &nested],
&api,
&AHashSet::default(),
&AHashSet::default(),
"Alef",
);
assert!(
all_plans.custom_section.contains("export type AlefOuter = AlefNested;"),
"actual:\n{}",
all_plans.custom_section
);
assert!(
all_plans.custom_section.contains("export type AlefNested = string;"),
"Nested must still be declared even though it was only ever reached as a nested \
reference before its own top-level turn ran;\nactual:\n{}",
all_plans.custom_section
);
}
#[test]
fn mutually_recursive_top_level_unions_both_get_declared() {
let a = EnumDef {
name: "MutualA".to_string(),
rust_path: "test_lib::MutualA".to_string(),
variants: vec![
tuple_variant("Leaf", TypeRef::String),
tuple_variant("Ref", TypeRef::Named("MutualB".to_string())),
],
serde_untagged: true,
..Default::default()
};
let b = EnumDef {
name: "MutualB".to_string(),
rust_path: "test_lib::MutualB".to_string(),
variants: vec![
tuple_variant("Leaf", TypeRef::String),
tuple_variant("Ref", TypeRef::Named("MutualA".to_string())),
],
serde_untagged: true,
..Default::default()
};
let mut api = empty_api();
api.enums = vec![a.clone(), b.clone()];
let all_plans = build_untagged_enum_ts_plans(&[&a, &b], &api, &AHashSet::default(), &AHashSet::default(), "Alef");
assert!(
all_plans
.custom_section
.contains("export type AlefMutualA = string | AlefMutualB;"),
"actual:\n{}",
all_plans.custom_section
);
assert!(
all_plans
.custom_section
.contains("export type AlefMutualB = string | AlefMutualA;"),
"actual:\n{}",
all_plans.custom_section
);
}