use std::any::Any;
use std::fmt::{Debug, Display};
use cratestack_core::DecimalValue;
pub trait DecimalLike: Debug + Display + Send + Sync {
fn clone_boxed(&self) -> Box<dyn DecimalLike>;
fn dyn_eq(&self, other: &dyn DecimalLike) -> bool;
fn as_any(&self) -> &dyn Any;
}
impl<T> DecimalLike for T
where
T: DecimalValue,
{
fn clone_boxed(&self) -> Box<dyn DecimalLike> {
Box::new(self.clone())
}
fn dyn_eq(&self, other: &dyn DecimalLike) -> bool {
other
.as_any()
.downcast_ref::<T>()
.is_some_and(|o| self == o)
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl Clone for Box<dyn DecimalLike> {
fn clone(&self) -> Self {
self.as_ref().clone_boxed()
}
}
impl PartialEq for Box<dyn DecimalLike> {
fn eq(&self, other: &Self) -> bool {
self.as_ref().dyn_eq(other.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq, PartialOrd)]
struct Fake(i64);
impl Display for Fake {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::str::FromStr for Fake {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map(Fake)
}
}
impl From<i64> for Fake {
fn from(value: i64) -> Self {
Fake(value)
}
}
#[test]
fn boxed_decimal_like_clones_and_compares_by_value() {
let a: Box<dyn DecimalLike> = Box::new(Fake(42));
let b = a.clone();
assert!(a == b);
let c: Box<dyn DecimalLike> = Box::new(Fake(7));
assert!(a != c);
}
#[test]
fn boxed_decimal_like_formats_via_display() {
let a: Box<dyn DecimalLike> = Box::new(Fake(42));
assert_eq!(a.to_string(), "42");
}
}