use super::runtime::to_pascal_case;
use crate::structured_ir::SchemaElements;
pub(crate) fn emit_conversion_traits(src: &mut String) {
src.push_str(
"/// Convert from a wire type to an application type.\n\
pub trait TryFromSbe<Wire>: Sized {\n\
type Error: core::fmt::Debug + core::fmt::Display;\n\
fn try_from_sbe(wire: Wire) -> Result<Self, Self::Error>;\n\
}\n\n\
/// Convert from an application type to a wire type.\n\
pub trait TryToSbe<Wire> {\n\
type Error: core::fmt::Debug + core::fmt::Display;\n\
fn try_to_sbe(&self) -> Result<Wire, Self::Error>;\n\
}\n\n",
);
}
pub(crate) fn generate_conversion_impl_blocks(
elements: &SchemaElements,
_conversions: &[crate::ConversionSelector],
domain_types: &[(crate::ConversionSelector, String)],
) -> String {
let mut out = String::new();
let span = proc_macro2::Span::call_site();
for (sel, ty) in domain_types {
if ty != "bool" {
continue;
}
let bt_name = match sel {
crate::ConversionSelector::NamedType(n) => to_pascal_case(n),
_ => continue,
};
let bt_ident = syn::Ident::new(&bt_name, span);
let ts = quote::quote! {
impl TryFromSbe<#bt_ident> for bool {
type Error = &'static str;
#[inline]
fn try_from_sbe(wire: #bt_ident) -> Result<Self, Self::Error> {
wire.as_bool().ok_or("null or unknown boolean discriminant")
}
}
impl TryToSbe<#bt_ident> for bool {
type Error = &'static str;
#[inline]
fn try_to_sbe(&self) -> Result<#bt_ident, Self::Error> {
Ok(#bt_ident::from(*self))
}
}
};
out.push_str(&ts.to_string());
}
let has_chrono_conv = domain_types.iter().any(|(sel, _)| {
matches!(sel, crate::ConversionSelector::SemanticType(st) if st == "UTCTimestamp")
});
for (sel, ty) in domain_types {
if ty != "rust_decimal::Decimal" {
continue;
}
let comp_name = match sel {
crate::ConversionSelector::NamedType(n) => n.as_str(),
_ => continue,
};
let Some(dec_composite) = elements.composites.iter().find(|c| c[0].name == comp_name)
else {
continue;
};
let dec_ident = syn::Ident::new(&to_pascal_case(comp_name), span);
let exponent_is_constant = dec_composite
.iter()
.find(|t| t.name == "exponent")
.map(|t| t.encoding.presence == crate::ir::Presence::Constant)
.unwrap_or(false);
let dec_new_call: proc_macro2::TokenStream = if exponent_is_constant {
quote::quote! { #dec_ident::new(mantissa) }
} else {
quote::quote! { #dec_ident::new(mantissa, -(self.scale() as i8)) }
};
let mantissa_is_optional = dec_composite
.iter()
.find(|t| t.name == "mantissa")
.map(|t| t.encoding.presence == crate::ir::Presence::Optional)
.unwrap_or(false);
let mantissa_expr: proc_macro2::TokenStream = if mantissa_is_optional {
quote::quote! { wire.mantissa().ok_or("null Decimal mantissa")? as i128 }
} else {
quote::quote! { wire.mantissa() as i128 }
};
let ts = quote::quote! {
impl TryFromSbe<#dec_ident> for rust_decimal::Decimal {
type Error = &'static str;
#[inline]
fn try_from_sbe(wire: #dec_ident) -> Result<Self, Self::Error> {
let mantissa = #mantissa_expr;
let exponent = wire.exponent() as i32;
let (mantissa, scale) = if exponent < 0 {
let scale = exponent.unsigned_abs();
(mantissa, scale)
} else {
let pow = 10i128.checked_pow(exponent as u32)
.ok_or("Decimal exponent overflow")?;
let scaled = mantissa.checked_mul(pow)
.ok_or("Decimal mantissa overflow")?;
(scaled, 0)
};
rust_decimal::Decimal::from_i128_with_scale(mantissa, scale)
.try_into()
.map_err(|_| "Decimal overflow")
}
}
impl TryToSbe<#dec_ident> for rust_decimal::Decimal {
type Error = &'static str;
#[inline]
fn try_to_sbe(&self) -> Result<#dec_ident, Self::Error> {
let mantissa: i64 = self.mantissa()
.try_into()
.map_err(|_| "Decimal mantissa overflow i64")?;
Ok(#dec_new_call)
}
}
};
out.push_str(&ts.to_string());
}
if has_chrono_conv {
let ts = quote::quote! {
impl TryFromSbe<u64> for chrono::DateTime<chrono::Utc> {
type Error = &'static str;
#[inline]
fn try_from_sbe(wire: u64) -> Result<Self, Self::Error> {
let secs = (wire / 1_000_000_000) as i64;
let nsec = (wire % 1_000_000_000) as u32;
chrono::DateTime::from_timestamp(secs, nsec)
.ok_or("timestamp out of range for DateTime<Utc>")
}
}
impl TryToSbe<u64> for chrono::DateTime<chrono::Utc> {
type Error = &'static str;
#[inline]
fn try_to_sbe(&self) -> Result<u64, Self::Error> {
let total_nanos = self.timestamp_nanos_opt()
.ok_or("timestamp_nanos overflow")?;
u64::try_from(total_nanos)
.map_err(|_| "timestamp out of u64 range")
}
}
};
out.push_str(&ts.to_string());
}
out
}