use std::fmt::Write as _;
use crate::schema::{ArrayElement, ContractSchema, PropertyKind};
use crate::targets::{header, number_literal, quoted_string, type_name};
pub fn emit(schema: &ContractSchema, epoch: u32) -> String {
let mut fields = String::new();
for property in &schema.properties {
let (mut chain, default_literal) = chain_and_default(&property.kind);
if let Some(literal) = default_literal {
let _ = write!(chain, ".default({literal})");
} else if !property.required {
chain.push_str(".optional()");
}
let _ = writeln!(fields, " {}: {chain},", property.name);
}
let name = type_name(schema);
format!(
"{header}import {{ z }} from \"zod\";\n\n\
export const {name}Schema = z\n .object({{\n{fields} }})\n .strict();\n\n\
export type {name} = z.infer<typeof {name}Schema>;\n",
header = header(schema, epoch, "//"),
)
}
fn chain_and_default(kind: &PropertyKind) -> (String, Option<String>) {
match kind {
PropertyKind::String {
min_length,
max_length,
format,
enum_values,
default,
} => {
let mut chain = match enum_values {
Some(values) => {
let list = values
.iter()
.map(|value| quoted_string(value))
.collect::<Vec<_>>()
.join(", ");
format!("z.enum([{list}])")
}
None if format.as_deref() == Some("email") => "z.email()".to_owned(),
None => "z.string()".to_owned(),
};
if enum_values.is_none() {
if let Some(min) = min_length {
let _ = write!(chain, ".min({min})");
}
if let Some(max) = max_length {
let _ = write!(chain, ".max({max})");
}
}
(chain, default.as_deref().map(quoted_string))
}
PropertyKind::Integer { default } => (
"z.number().int()".to_owned(),
default.map(|value| value.to_string()),
),
PropertyKind::Number { default } => ("z.number()".to_owned(), default.map(number_literal)),
PropertyKind::Boolean { default } => (
"z.boolean()".to_owned(),
default.map(|value| value.to_string()),
),
PropertyKind::Array { element } => {
let inner = match element {
ArrayElement::String => "z.string()",
ArrayElement::Integer => "z.number().int()",
ArrayElement::Number => "z.number()",
ArrayElement::Boolean => "z.boolean()",
};
(format!("z.array({inner})"), None)
}
}
}