use sigil_stitch::prelude::*;
fn main() {
println!("=== Builder API ===\n");
let builder_output = builder_approach();
println!("{builder_output}");
println!("=== sigil_quote! Macro ===\n");
let macro_output = macro_approach();
println!("{macro_output}");
}
fn builder_approach() -> String {
let user_type = TypeName::importable_type("./models", "User");
let not_found = TypeName::importable_type("./errors", "NotFoundError");
let event_emitter = TypeName::importable("events", "EventEmitter");
let status_enum = TypeSpec::builder("Status", TypeKind::Enum)
.visibility(Visibility::Public)
.add_variant(
EnumVariantSpec::builder("Active")
.value(CodeBlock::of("%S", (StringLitArg("active".into()),)).unwrap())
.build()
.unwrap(),
)
.add_variant(
EnumVariantSpec::builder("Inactive")
.value(CodeBlock::of("%S", (StringLitArg("inactive".into()),)).unwrap())
.build()
.unwrap(),
)
.add_variant(
EnumVariantSpec::builder("Suspended")
.value(CodeBlock::of("%S", (StringLitArg("suspended".into()),)).unwrap())
.build()
.unwrap(),
)
.build()
.unwrap();
let repo_iface = TypeSpec::builder("Repository", TypeKind::Interface)
.visibility(Visibility::Public)
.add_type_param(TypeParamSpec::new("T"))
.add_method(
FunSpec::builder("findById")
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::union(vec![
TypeName::primitive("T"),
TypeName::primitive("null"),
])],
))
.add_param(ParameterSpec::new("id", TypeName::primitive("string")).unwrap())
.build()
.unwrap(),
)
.add_method(
FunSpec::builder("save")
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::primitive("void")],
))
.add_param(ParameterSpec::new("entity", TypeName::primitive("T")).unwrap())
.build()
.unwrap(),
)
.add_method(
FunSpec::builder("findAll")
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::array(TypeName::primitive("T"))],
))
.add_param(
ParameterSpec::builder("limit", TypeName::primitive("number"))
.default_value(CodeBlock::of("10", ()).unwrap())
.build()
.unwrap(),
)
.build()
.unwrap(),
)
.build()
.unwrap();
let tb = TypeSpec::builder("UserService", TypeKind::Class)
.visibility(Visibility::Public)
.doc("Service for managing users.")
.add_field(
FieldSpec::builder("userRepo", TypeName::primitive("UserRepository"))
.visibility(Visibility::Private)
.is_readonly()
.build()
.unwrap(),
)
.add_field(
FieldSpec::builder("nickname", TypeName::primitive("string"))
.is_optional()
.build()
.unwrap(),
)
.add_field(
FieldSpec::builder(
"onChange",
TypeName::function(
vec![TypeName::primitive("User"), TypeName::primitive("string")],
TypeName::primitive("void"),
),
)
.is_optional()
.build()
.unwrap(),
);
let get_body = CodeBlock::of("return this.userRepo.count;", ()).unwrap();
let prop = PropertySpec::builder("totalUsers", TypeName::primitive("number"))
.getter(get_body)
.build()
.unwrap();
let mut body = CodeBlock::builder();
body.add_statement(
"const user = await this.userRepo.findById(%S)",
(StringLitArg("id".into()),),
);
body.begin_control_flow("if (!user)", ());
body.add_statement("throw new %T('User not found')", (not_found,));
body.end_control_flow();
body.add_statement("return user", ());
let get_user = FunSpec::builder("getUser")
.is_async()
.add_param(ParameterSpec::new("id", TypeName::primitive("string")).unwrap())
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![user_type],
))
.body(body.build().unwrap())
.build()
.unwrap();
let mut log_body = CodeBlock::builder();
log_body.add("const emitter = new %T();", (event_emitter,));
log_body.add_line();
log_body.add_statement("emitter.emit('log', message)", ());
let log_fn = FunSpec::builder("log")
.is_static()
.add_param(ParameterSpec::new("message", TypeName::primitive("string")).unwrap())
.body(log_body.build().unwrap())
.build()
.unwrap();
let tb = tb
.add_property(prop)
.add_method(get_user)
.add_method(log_fn);
FileSpec::builder("UserService.ts")
.add_import(ImportSpec::side_effect("reflect-metadata"))
.add_import(ImportSpec::named_as("lodash", "merge", "deepMerge"))
.add_type(status_enum)
.add_type(repo_iface)
.add_type(tb.build().unwrap())
.build()
.unwrap()
.render(80)
.unwrap()
}
fn macro_approach() -> String {
let user_type = TypeName::importable_type("./models", "User");
let not_found = TypeName::importable_type("./errors", "NotFoundError");
let event_emitter = TypeName::importable("events", "EventEmitter");
let status_enum = TypeSpec::builder("Status", TypeKind::Enum)
.visibility(Visibility::Public)
.add_variant(
EnumVariantSpec::builder("Active")
.value(CodeBlock::of("%S", (StringLitArg("active".into()),)).unwrap())
.build()
.unwrap(),
)
.add_variant(
EnumVariantSpec::builder("Inactive")
.value(CodeBlock::of("%S", (StringLitArg("inactive".into()),)).unwrap())
.build()
.unwrap(),
)
.add_variant(
EnumVariantSpec::builder("Suspended")
.value(CodeBlock::of("%S", (StringLitArg("suspended".into()),)).unwrap())
.build()
.unwrap(),
)
.build()
.unwrap();
let repo_iface = TypeSpec::builder("Repository", TypeKind::Interface)
.visibility(Visibility::Public)
.add_type_param(TypeParamSpec::new("T"))
.add_method(
FunSpec::builder("findById")
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::union(vec![
TypeName::primitive("T"),
TypeName::primitive("null"),
])],
))
.add_param(ParameterSpec::new("id", TypeName::primitive("string")).unwrap())
.build()
.unwrap(),
)
.add_method(
FunSpec::builder("save")
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::primitive("void")],
))
.add_param(ParameterSpec::new("entity", TypeName::primitive("T")).unwrap())
.build()
.unwrap(),
)
.add_method(
FunSpec::builder("findAll")
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::array(TypeName::primitive("T"))],
))
.add_param(
ParameterSpec::builder("limit", TypeName::primitive("number"))
.default_value(CodeBlock::of("10", ()).unwrap())
.build()
.unwrap(),
)
.build()
.unwrap(),
)
.build()
.unwrap();
let body = sigil_quote!(TypeScript {
const user = await this.userRepo.findById($S("id"));
if(!user) {
throw new $T(not_found)($S("User not found"));
}
return user;
})
.unwrap();
let get_user = FunSpec::builder("getUser")
.is_async()
.add_param(ParameterSpec::new("id", TypeName::primitive("string")).unwrap())
.returns(TypeName::generic(
TypeName::primitive("Promise"),
vec![user_type],
))
.body(body)
.build()
.unwrap();
let log_body = sigil_quote!(TypeScript {
const emitter = new $T(event_emitter)();
emitter.emit($S("log"), message);
})
.unwrap();
let log_fn = FunSpec::builder("log")
.is_static()
.add_param(ParameterSpec::new("message", TypeName::primitive("string")).unwrap())
.body(log_body)
.build()
.unwrap();
let get_body = sigil_quote!(TypeScript {
return this.userRepo.count;
})
.unwrap();
let prop = PropertySpec::builder("totalUsers", TypeName::primitive("number"))
.getter(get_body)
.build()
.unwrap();
let tb = TypeSpec::builder("UserService", TypeKind::Class)
.visibility(Visibility::Public)
.doc("Service for managing users.")
.add_field(
FieldSpec::builder("userRepo", TypeName::primitive("UserRepository"))
.visibility(Visibility::Private)
.is_readonly()
.build()
.unwrap(),
)
.add_field(
FieldSpec::builder("nickname", TypeName::primitive("string"))
.is_optional()
.build()
.unwrap(),
)
.add_field(
FieldSpec::builder(
"onChange",
TypeName::function(
vec![TypeName::primitive("User"), TypeName::primitive("string")],
TypeName::primitive("void"),
),
)
.is_optional()
.build()
.unwrap(),
)
.add_property(prop)
.add_method(get_user)
.add_method(log_fn);
FileSpec::builder("UserService.ts")
.add_import(ImportSpec::side_effect("reflect-metadata"))
.add_import(ImportSpec::named_as("lodash", "merge", "deepMerge"))
.add_type(status_enum)
.add_type(repo_iface)
.add_type(tb.build().unwrap())
.build()
.unwrap()
.render(80)
.unwrap()
}