use crate::dispatcher::{DispatchRule, FailureScore, MatchScore};
#[derive(Debug)]
pub struct Any {
any: Box<dyn SerializableAny>,
tag: &'static str,
}
pub const MATCH_FAIL: FailureScore = FailureScore(10_000);
impl Any {
pub fn new<T>(any: T, tag: &'static str) -> Self
where
T: serde::Serialize + std::fmt::Debug + 'static,
{
Self {
any: Box::new(any),
tag,
}
}
pub fn raw<T>(any: T, repr: serde_json::Value, tag: &'static str) -> Self
where
T: std::fmt::Debug + 'static,
{
Self {
any: Box::new(Raw::new(any, repr)),
tag,
}
}
pub fn tag(&self) -> &'static str {
self.tag
}
pub fn type_id(&self) -> std::any::TypeId {
self.any.as_any().type_id()
}
#[must_use = "this function has no side effects"]
pub fn is<T>(&self) -> bool
where
T: std::any::Any,
{
self.any.as_any().is::<T>()
}
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: std::any::Any,
{
self.any.as_any().downcast_ref::<T>()
}
pub fn try_match<'a, T, U>(&'a self) -> Result<MatchScore, FailureScore>
where
U: DispatchRule<&'a T>,
T: 'static,
{
if let Some(cast) = self.downcast_ref::<T>() {
U::try_match(&cast)
} else {
Err(MATCH_FAIL)
}
}
pub fn convert<'a, T, U>(&'a self) -> anyhow::Result<U>
where
U: DispatchRule<&'a T>,
anyhow::Error: From<U::Error>,
T: 'static,
{
if let Some(cast) = self.downcast_ref::<T>() {
Ok(U::convert(cast)?)
} else {
Err(anyhow::Error::msg("invalid dispatch"))
}
}
pub fn description<'a, T, U>(
f: &mut std::fmt::Formatter<'_>,
from: Option<&&'a Self>,
tag: impl std::fmt::Display,
) -> std::fmt::Result
where
U: DispatchRule<&'a T>,
T: 'static,
{
match from {
Some(this) => match this.downcast_ref::<T>() {
Some(a) => U::description(f, Some(&a)),
None => write!(
f,
"expected tag \"{}\" - instead got \"{}\"",
tag,
this.tag(),
),
},
None => {
writeln!(f, "tag \"{}\"", tag)?;
U::description(f, None::<&&T>)
}
}
}
pub fn serialize(&self) -> Result<serde_json::Value, serde_json::Error> {
self.any.dump()
}
}
#[macro_export]
macro_rules! describeln {
($writer:ident, $fmt:literal) => {
writeln!($writer, concat!(" ", $fmt))
};
($writer:ident, $fmt:literal, $($args:expr),* $(,)?) => {
writeln!($writer, concat!(" ", $fmt), $($args,)*)
};
}
trait SerializableAny: std::fmt::Debug {
fn as_any(&self) -> &dyn std::any::Any;
fn dump(&self) -> Result<serde_json::Value, serde_json::Error>;
}
impl<T> SerializableAny for T
where
T: std::any::Any + serde::Serialize + std::fmt::Debug,
{
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn dump(&self) -> Result<serde_json::Value, serde_json::Error> {
serde_json::to_value(self)
}
}
#[derive(Debug)]
struct Raw<T> {
value: T,
repr: serde_json::Value,
}
impl<T> Raw<T> {
fn new(value: T, repr: serde_json::Value) -> Self {
Self { value, repr }
}
}
impl<T> SerializableAny for Raw<T>
where
T: std::any::Any + std::fmt::Debug,
{
fn as_any(&self) -> &dyn std::any::Any {
&self.value
}
fn dump(&self) -> Result<serde_json::Value, serde_json::Error> {
Ok(self.repr.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::datatype::{self, DataType, Type};
#[test]
fn test_new() {
let x = Any::new(42usize, "my-tag");
assert_eq!(x.tag(), "my-tag");
assert_eq!(x.type_id(), std::any::TypeId::of::<usize>());
assert!(x.is::<usize>());
assert!(!x.is::<u32>());
assert_eq!(*x.downcast_ref::<usize>().unwrap(), 42);
assert!(x.downcast_ref::<u32>().is_none());
assert!(!x.is::<Raw<usize>>());
assert!(!x.is::<Raw<u32>>());
assert!(x.downcast_ref::<Raw<usize>>().is_none());
assert!(x.downcast_ref::<Raw<u32>>().is_none());
assert_eq!(
x.serialize().unwrap(),
serde_json::Value::Number(serde_json::value::Number::from(42usize))
);
}
#[test]
fn test_raw() {
let repr = serde_json::json!(1.5);
let x = Any::raw(42usize, repr, "my-tag");
assert_eq!(x.tag(), "my-tag");
assert_eq!(x.type_id(), std::any::TypeId::of::<usize>());
assert!(x.is::<usize>());
assert!(!x.is::<u32>());
assert_eq!(*x.downcast_ref::<usize>().unwrap(), 42);
assert!(x.downcast_ref::<u32>().is_none());
assert!(!x.is::<Raw<usize>>());
assert!(!x.is::<Raw<u32>>());
assert!(x.downcast_ref::<Raw<usize>>().is_none());
assert!(x.downcast_ref::<Raw<u32>>().is_none());
assert_eq!(
x.serialize().unwrap(),
serde_json::Value::Number(serde_json::value::Number::from_f64(1.5).unwrap())
);
}
#[test]
fn test_try_match() {
let value = Any::new(DataType::Float32, "random-tag");
assert_eq!(
value.try_match::<DataType, Type<f32>>().unwrap(),
MatchScore(0),
);
assert_eq!(
value.try_match::<DataType, Type<f64>>().unwrap_err(),
datatype::MATCH_FAIL,
);
let value = Any::new(0usize, "");
assert_eq!(
value.try_match::<DataType, Type<f32>>().unwrap_err(),
MATCH_FAIL,
);
}
#[test]
fn test_convert() {
let value = Any::new(DataType::Float32, "random-tag");
let _: Type<f32> = value.convert::<DataType, _>().unwrap();
let value = Any::new(0usize, "random-rag");
let err = value.convert::<DataType, Type<f32>>().unwrap_err();
let msg = err.to_string();
assert!(msg.contains("invalid dispatch"), "{}", msg);
}
#[test]
#[should_panic(expected = "invalid dispatch")]
fn test_convert_inner_error() {
let value = Any::new(DataType::Float32, "random-tag");
let _ = value.convert::<DataType, Type<u64>>();
}
#[test]
fn test_description() {
struct Display(Option<Any>);
impl std::fmt::Display for Display {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
Some(v) => Any::description::<DataType, Type<f32>>(f, Some(&v), "my-tag"),
None => Any::description::<DataType, Type<f32>>(f, None, "my-tag"),
}
}
}
assert_eq!(Display(None).to_string(), "tag \"my-tag\"\nfloat32",);
assert_eq!(
Display(Some(Any::new(DataType::Float32, ""))).to_string(),
"successful match",
);
assert_eq!(
Display(Some(Any::new(DataType::UInt64, ""))).to_string(),
"expected \"float32\" but found \"uint64\"",
);
assert_eq!(
Display(Some(Any::new(0usize, ""))).to_string(),
"expected tag \"my-tag\" - instead got \"\"",
);
}
}