use insta::{assert_debug_snapshot, assert_snapshot};
use std::str::FromStr;
#[test]
fn test_simple() {
fn handle_citrus(fruit_id: u16) -> &'static str {
csv_codegen::csv_template!("fruits.csv", #each(category == "citrus") {
match fruit_id {
#each {
#({id}) => concat!("Found citrus: ", #("{name}")),
}
_ => "Not a citrus fruit",
}
})
}
assert_eq!(handle_citrus(5), "Not a citrus fruit");
assert_eq!(handle_citrus(3), "Found citrus: Lime");
}
#[test]
fn test_subquery() {
csv_codegen::csv_template!("products.csv", #each(active == true) {
#[derive(Debug, PartialEq)]
enum #Type({category}) {
#each {
#Type({name}),
}
}
impl FromStr for #Type({category}) {
type Err = ();
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
#each {
#("{name}") => Ok(Self::#Type({name})),
}
_ => Err(()),
}
}
}
});
let product: Power = "Phone Charger".parse().unwrap();
assert_eq!(product, Power::PhoneCharger);
}
#[test]
fn i18n_example() {
csv_codegen::csv_template!("i18n.csv", {
enum Language {
En,
#each {
#Type({language}),
}
}
});
macro_rules! format_i18n {
($language:expr, $format_str_en:literal @ $context:literal $($args:tt)*) => {
csv_codegen::csv_template!("i18n.csv", #find(format_str_en == $format_str_en){
match $language {
Language::En => format!(#("{format_str_en}") $($args)*),
#each {
Language::#Type({language}) => format!(#("{format_str}") $($args)*),
}
}
})
};
($language:expr, $format_str_en:literal $($args:tt)*) => {
format_i18n!($language, $format_str_en@"" $($args)*)
};
}
assert_eq!(
format_i18n!(Language::De, "Close {item}"@"verb_action", item = "#2"),
"#2 schließen"
);
assert_eq!(
format_i18n!(Language::Es, "File not found"),
"Archivo no encontrado"
);
assert_eq!(
format_i18n!(Language::En, "Save {count} items", count = 2),
"Save 2 items"
)
}
#[test]
fn test_categories_ordered() {
csv_codegen::csv_template!("products.csv", #each {
fn #ident({category})() -> Vec<&'static str> {
vec!(
#each {
#("{name}"),
}
)
}
});
assert_debug_snapshot!(accessories(), @r###"
[
"Smart Watch",
"Tablet Case",
]
"###);
assert_debug_snapshot!(computer_peripherals(), @r###"
[
"Gaming Mouse",
"Keyboard",
"Webcam",
]
"###);
}
#[test]
fn test_prefix() {
csv_codegen::csv_template!("products.csv", {
fn get_price(name: &str) -> Option<f64> {
match name {
#each(price != ""){
#("{name}") => Some(#({price}_f64)),
}
_ => None,
}
}
});
assert_snapshot!(get_price("Ergonomic Chair").unwrap(), @"399.99");
}
#[test]
fn pivot() {
csv_codegen::csv_template!("sales.csv", pivot("q1_sales"..="q4_sales", quarter, sales), #each {
#[allow(unused)]
struct #Type({product}Sales);
impl #Type({product}Sales) {
#each {
#[allow(unused)]
pub const #CONST({quarter}): u32 = #({sales}_u32);
}
}
});
assert_eq!(SmartWatchSales::Q_2_SALES, 180);
}
#[derive(Debug, Clone)]
pub struct HighQualityProduct {
pub name: &'static str,
pub quality_metrics: QualityMetrics,
pub certification: CertificationLevel,
}
#[derive(Debug, Clone)]
pub struct QualityMetrics {
pub defect_rate: f32,
pub batch_size: u32,
pub factory: &'static str,
pub line: &'static str,
}
csv_codegen::csv_template!("quality_data.csv", {
#[derive(Debug, Clone, PartialEq)]
pub enum CertificationLevel {
#each {
#Type({certification_status}),
}
}
});
csv_codegen::csv_template!("quality_data.csv", #each {
pub mod #ident({factory}_manufacturing) {
use super::*;
#each {
pub struct #Type(Line{line}Quality) {
pub line_identifier: &'static str,
pub tier_one_products: Vec<HighQualityProduct>,
}
impl #Type(Line{line}Quality) {
pub fn build() -> Self {
Self {
line_identifier: #("{line}"),
tier_one_products: vec![
#each(quality_tier == "tier_1"){
HighQualityProduct {
name: #("{product}"),
quality_metrics: QualityMetrics {
defect_rate: #({defect_rate}_f32),
batch_size: #({batch_size}_u32),
factory: #("{factory}"),
line: #("{line}"),
},
certification: CertificationLevel::#Type({certification_status}),
},
}
],
}
}
}
}
}
});
#[test]
fn complex() {
let department = university_hospital_medical::cardiac_system();
assert_debug_snapshot!(department, @r###"
Department {
name: "cardiac",
patients: [
Patient {
id: "p018",
vitals: Vitals {
age: 66,
bp: 100,
hr: 109,
temp: 36.8,
},
assessment: FullLabs {
wbc: 6500,
creatinine: 1.7,
risk_score: 1,
},
},
Patient {
id: "p019",
vitals: Vitals {
age: 54,
bp: 115,
hr: 94,
temp: 38.1,
},
assessment: VitalsOnly {
warning_score: 0,
infection_risk: Clinical {
fever: true,
},
},
},
],
}
"###)
}
csv_codegen::csv_template!("patient_data.csv", #each {
pub mod #ident({hospital}_medical) {
use super::*;
#each {
pub fn #ident({department}_system)() -> Department { Department {
name: #("{department}"),
patients: vec![
#each(age != "" && systolic_bp != "" && heart_rate != ""){
Patient {
id: #("{patient_id}"),
vitals: Vitals {
age: #({age}_u8),
bp: #({systolic_bp}_u16),
hr: #({heart_rate}_u16),
temp: #({temperature}_f32),
},
assessment: #find(white_cell_count != "" && creatinine != ""){
Assessment::FullLabs {
wbc: #({white_cell_count}_u32),
creatinine: #({creatinine}_f32),
risk_score: calculate_risk(#({systolic_bp}_u16), #({creatinine}_f32)),
}
}
#else{
Assessment::VitalsOnly {
warning_score: calculate_warning(
#({systolic_bp}_u16),
#({heart_rate}_u16)
),
infection_risk: #find(white_cell_count != ""){
InfectionRisk::Lab(#({white_cell_count}_u32))
}
#else{
InfectionRisk::Clinical {
fever: #({temperature}_f32) > 38.0,
}
},
}
},
},
}
],
}}
}
}
});
#[derive(Debug)]
pub struct Department {
pub name: &'static str,
pub patients: Vec<Patient>,
}
#[derive(Debug, Clone)]
pub struct Patient {
pub id: &'static str,
pub vitals: Vitals,
pub assessment: Assessment,
}
#[derive(Debug, Clone)]
pub struct Vitals {
pub age: u8,
pub bp: u16,
pub hr: u16,
pub temp: f32,
}
#[derive(Debug, Clone)]
pub enum Assessment {
FullLabs {
wbc: u32,
creatinine: f32,
risk_score: u8,
},
VitalsOnly {
warning_score: u8,
infection_risk: InfectionRisk,
},
}
#[derive(Debug, Clone)]
pub enum InfectionRisk {
Lab(u32),
Clinical {
fever: bool,
},
}
fn calculate_risk(bp: u16, creatinine: f32) -> u8 {
let bp_score = if bp < 90 {
3
} else if bp < 100 {
1
} else {
0
};
let renal_score = if creatinine >= 2.0 {
2
} else if creatinine >= 1.2 {
1
} else {
0
};
bp_score + renal_score
}
fn calculate_warning(bp: u16, hr: u16) -> u8 {
let mut score = 0;
if bp <= 90 {
score += 3;
} else if bp <= 100 {
score += 1;
}
if hr >= 131 {
score += 3;
} else if hr >= 111 {
score += 2;
}
score
}
#[derive(Debug, Clone, PartialEq)]
pub struct EventConfig {
pub event_id: &'static str,
pub timeout_ms: u32,
pub retry_policy: RetryPolicy,
pub handlers: Vec<HandlerConfig>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RetryPolicy {
None,
Limited { max_attempts: u8, backoff_ms: u32 },
}
#[derive(Debug, Clone, PartialEq)]
pub struct HandlerConfig {
pub name: &'static str,
pub priority: EventPriority,
pub conditions: Option<Condition>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Condition {
pub field: &'static str,
pub operator: ConditionOp,
pub value: &'static str,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConditionOp {
Equals,
Contains,
}
csv_codegen::csv_template!("events.csv", {
#[derive(Debug, Clone, PartialEq)]
pub enum EventType {
#each {
#Type({event_type}),
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum EventPriority {
#each {
#Type({priority}),
}
}
});
csv_codegen::csv_template!("events.csv", #each {
pub mod #ident({service}_events) {
use super::*;
pub struct #Type({service}EventHandler) {
pub service_name: &'static str,
pub default_timeout: u32,
}
impl #Type({service}EventHandler) {
pub fn handle_event(&self, event_type: EventType) -> Option<EventConfig> {
match event_type {
#each {
EventType::#Type({event_type}) => Some(EventConfig {
event_id: #("{event_id}"),
timeout_ms: #find(timeout_override != ""){
#({timeout_override}_u32)
} #else {
self.default_timeout
},
retry_policy: #find(max_retries != ""){
RetryPolicy::Limited {
max_attempts: #({max_retries}_u8),
backoff_ms: #find(backoff_ms != ""){
#({backoff_ms}_u32)
} #else {
1000
},
}
} #else {
RetryPolicy::None
},
handlers: vec![
#each(handler_1 != ""){
HandlerConfig {
name: #("{handler_1}"),
priority: EventPriority::#Type({priority}),
conditions: #find(condition_field != "" && condition_value != ""){
Some(Condition {
field: #("{condition_field}"),
operator: #find(condition_operator == "equals"){
ConditionOp::Equals
} #else {
ConditionOp::Contains
},
value: #("{condition_value}"),
})
} #else {
None
},
},
}
#each(handler_2 != ""){
HandlerConfig {
name: #("{handler_2}"),
priority: EventPriority::#Type({priority}),
conditions: None,
},
}
],
}),
}
_ => None,
}
}
pub const SERVICE_METRICS: &'static [(&'static str, u32)] = &[
#each(avg_processing_time != ""){
(#("{event_type}"), #({avg_processing_time}_u32)),
}
];
}
}
});
#[test]
fn test_event_driven_system() {
let payment_handler = payment_events::PaymentEventHandler {
service_name: "payment",
default_timeout: 3000,
};
let signup_config = payment_handler.handle_event(EventType::UserSignup).unwrap();
assert_eq!(signup_config.event_id, "usr_001");
assert_eq!(signup_config.timeout_ms, 5000); assert_eq!(signup_config.handlers.len(), 2);
assert_eq!(signup_config.handlers[0].name, "validate_email");
assert_eq!(signup_config.handlers[0].priority, EventPriority::High);
if let Some(condition) = &signup_config.handlers[0].conditions {
assert_eq!(condition.field, "email");
assert_eq!(condition.operator, ConditionOp::Contains);
assert_eq!(condition.value, "@company.com");
}
match signup_config.retry_policy {
RetryPolicy::Limited {
max_attempts,
backoff_ms,
} => {
assert_eq!(max_attempts, 3);
assert_eq!(backoff_ms, 2000);
}
_ => panic!("Expected Limited retry policy"),
}
let notification_handler = notification_events::NotificationEventHandler {
service_name: "notification",
default_timeout: 10000,
};
let email_config = notification_handler
.handle_event(EventType::EmailSent)
.unwrap();
assert_eq!(email_config.timeout_ms, 10000); assert_eq!(email_config.handlers[0].conditions, None);
let metrics = payment_events::PaymentEventHandler::SERVICE_METRICS;
assert!(metrics.len() >= 2);
assert!(metrics.iter().any(|(event, _)| *event == "UserSignup"));
}
#[test]
fn test_implicit_multiple_groups_error() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/implicit_multiple_groups.rs");
}
#[test]
fn test_find_requires_condition_error() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/find_no_condition.rs");
}
#[test]
fn test_find_multiple_rows_error() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/find_multiple_rows.rs");
}
#[test]
fn test_having_directive_with_iteration() {
let depts: &[(&str, &str, &[&str])] = csv_codegen::csv_template!("departments.csv", {
&[
#each{
(
#("{department}_team"), #having(union_rep == true){#("{name}")}, &[
#each{ #("{name}"), }
]
),
}
]
});
assert_debug_snapshot!(depts, @r#"
[
(
"engineering_team",
"Frank",
[
"Eve",
"Frank",
],
),
(
"security_team",
"Alice",
[
"Alice",
"Bob",
],
),
]
"#);
}
#[test]
fn test_having_with_else_error() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/having_with_else.rs");
}
#[test]
fn test_template_syntax_error() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/template_syntax_error.rs");
}
#[test]
fn test_non_exhaustive_match() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/non_exhaustive_match.rs");
}
#[test]
fn test_multiple_having_conditions_working() {
let result = csv_codegen::csv_template!("departments.csv", #each {
(
#("{department}"),
#having(union_rep == true){#("{name}")},
#having(employee_id == "emp005"){#("{name}")},
)
});
assert_debug_snapshot!(result, @r#"
(
"engineering",
"Frank",
"Eve",
)
"#);
}