use std::fmt::Debug;
use void::{ResultVoidExt, Void};
use crate::{Diff, Differ, StructDiffer, TupleDiffer, SeqDiffer, SetDiffer, MapDiffer};
pub fn record_diff<T: Diff>(a: &T, b: &T) -> Value {
Diff::diff(a, b, ValueRecorder).void_unwrap()
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Value {
Same(String, String),
Difference(String, String),
Newtype(&'static str, Box<Value>),
Struct(Struct),
Tuple(Tuple),
Enum(Enum),
Sequence(Vec<Element>),
Set(Vec<Element>),
Map(Vec<(String, Element)>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Struct {
pub name: &'static str,
pub fields: Vec<(&'static str, Option<Value>)>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Tuple {
pub name: &'static str,
pub fields: Vec<Option<Value>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Enum {
pub name: &'static str,
pub variant: Variant,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Variant {
Struct(Struct),
Tuple(Tuple),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Element {
LeftOnly(String),
RightOnly(String),
Both(Value),
}
struct ValueRecorder;
impl Differ for ValueRecorder {
type Ok = Value;
type Err = Void;
type StructDiffer = StructRecorder;
type StructVariantDiffer = StructRecorder;
type TupleDiffer = TupleRecorder;
type TupleVariantDiffer = TupleRecorder;
type SeqDiffer = SequenceRecorder;
type MapDiffer = MapRecorder;
type SetDiffer = SequenceRecorder;
fn difference(self, a: &Debug, b: &Debug) -> Result<Self::Ok, Self::Err> {
Ok(Value::Difference(format!("{:?}", a), format!("{:?}", b)))
}
fn same(self, a: &Debug, b: &Debug) -> Result<Self::Ok, Self::Err> {
Ok(Value::Same(format!("{:?}", a), format!("{:?}", b)))
}
fn diff_newtype<T: ?Sized>(
self,
ty: &'static str,
a: &T,
b: &T,
) -> Result<Self::Ok, Self::Err>
where
T: Diff,
{
Ok(Value::Newtype(
ty,
Box::new(Diff::diff(a, b, ValueRecorder).void_unwrap()),
))
}
fn begin_struct(self, ty: &'static str) -> Self::StructDiffer {
StructRecorder(Struct {
name: ty,
fields: vec![],
}, OutputStyle::Raw)
}
fn begin_struct_variant(
self,
ty: &'static str,
var: &'static str,
) -> Self::StructVariantDiffer {
StructRecorder(Struct {
name: var,
fields: vec![],
}, OutputStyle::VariantOf(ty))
}
fn begin_tuple(self, ty: &'static str) -> Self::TupleDiffer {
TupleRecorder(Tuple {
name: ty,
fields: vec![],
}, OutputStyle::Raw)
}
fn begin_tuple_variant(
self,
ty: &'static str,
var: &'static str,
) -> Self::TupleVariantDiffer {
TupleRecorder(Tuple {
name: var,
fields: vec![],
}, OutputStyle::VariantOf(ty))
}
fn begin_seq(self) -> Self::SeqDiffer {
SequenceRecorder(vec![])
}
fn begin_map(self) -> Self::MapDiffer {
MapRecorder(vec![])
}
fn begin_set(self) -> Self::SetDiffer {
SequenceRecorder(vec![])
}
}
enum OutputStyle {
Raw,
VariantOf(&'static str),
}
struct StructRecorder(Struct, OutputStyle);
impl StructDiffer for StructRecorder {
type Ok = Value;
type Err = Void;
fn diff_field<T: ?Sized>(&mut self, name: &'static str, a: &T, b: &T)
where
T: Diff,
{
let val = Diff::diff(a, b, ValueRecorder).void_unwrap();
self.0.fields.push((name, Some(val)))
}
fn skip_field<T: ?Sized>(&mut self, name: &'static str) {
self.0.fields.push((name, None))
}
fn end(self) -> Result<Self::Ok, Self::Err> {
match self.1 {
OutputStyle::Raw => Ok(Value::Struct(self.0)),
OutputStyle::VariantOf(ty) => Ok(Value::Enum(Enum {
name: ty,
variant: Variant::Struct(self.0),
})),
}
}
}
struct TupleRecorder(Tuple, OutputStyle);
impl TupleDiffer for TupleRecorder {
type Ok = Value;
type Err = Void;
fn diff_field<T: ?Sized>(&mut self, a: &T, b: &T)
where
T: Diff,
{
let val = Diff::diff(a, b, ValueRecorder).void_unwrap();
self.0.fields.push(Some(val))
}
fn skip_field<T: ?Sized>(&mut self) {
self.0.fields.push(None)
}
fn end(self) -> Result<Self::Ok, Self::Err> {
match self.1 {
OutputStyle::Raw => Ok(Value::Tuple(self.0)),
OutputStyle::VariantOf(ty) => Ok(Value::Enum(Enum {
name: ty,
variant: Variant::Tuple(self.0),
})),
}
}
}
struct SequenceRecorder(Vec<Element>);
impl SeqDiffer for SequenceRecorder {
type Ok = Value;
type Err = Void;
fn diff_element<T: ?Sized>(&mut self, a: &T, b: &T)
where
T: Diff
{
self.0.push(Element::Both(Diff::diff(a, b, ValueRecorder).void_unwrap()))
}
fn left_excess<T: ?Sized>(&mut self, a: &T)
where
T: Diff
{
self.0.push(Element::LeftOnly(format!("{:?}", a)))
}
fn right_excess<T: ?Sized>(&mut self, a: &T)
where
T: Diff
{
self.0.push(Element::RightOnly(format!("{:?}", a)))
}
fn end(self) -> Result<Self::Ok, Self::Err> {
Ok(Value::Sequence(self.0))
}
}
impl SetDiffer for SequenceRecorder {
type Ok = Value;
type Err = Void;
fn diff_equal<T: ?Sized>(&mut self, a: &T, b: &T)
where
T: Diff
{
self.0.push(Element::Both(Diff::diff(a, b, ValueRecorder).void_unwrap()))
}
fn only_in_left<T: ?Sized>(&mut self, a: &T)
where
T: Diff
{
self.0.push(Element::LeftOnly(format!("{:?}", a)))
}
fn only_in_right<T: ?Sized>(&mut self, a: &T)
where
T: Diff
{
self.0.push(Element::RightOnly(format!("{:?}", a)))
}
fn end(self) -> Result<Self::Ok, Self::Err> {
Ok(Value::Set(self.0))
}
}
struct MapRecorder(Vec<(String, Element)>);
impl MapDiffer for MapRecorder {
type Ok = Value;
type Err = Void;
fn diff_entry<K, V>(&mut self, key: &K, a: &V, b: &V)
where
K: ?Sized + Debug,
V: ?Sized + Diff,
{
let key = format!("{:?}", key);
let diff = Diff::diff(a, b, ValueRecorder).void_unwrap();
self.0.push((key, Element::Both(diff)))
}
fn only_in_left<K, V>(&mut self, key: &K, a: &V)
where
K: ?Sized + Debug,
V: ?Sized + Diff,
{
let key = format!("{:?}", key);
self.0.push((key, Element::LeftOnly(format!("{:?}", a))))
}
fn only_in_right<K, V>(&mut self, key: &K, a: &V)
where
K: ?Sized + Debug,
V: ?Sized + Diff,
{
let key = format!("{:?}", key);
self.0.push((key, Element::RightOnly(format!("{:?}", a))))
}
fn end(self) -> Result<Self::Ok, Self::Err> {
Ok(Value::Map(self.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unit() {
let diff = Diff::diff(&(), &(), ValueRecorder).void_unwrap();
assert_eq!(diff, Value::Same("()".into(), "()".into()));
}
#[test]
fn int() {
let diff = Diff::diff(&0u32, &0, ValueRecorder).void_unwrap();
assert_eq!(diff, Value::Same("0".into(), "0".into()));
let diff = Diff::diff(&0u32, &1, ValueRecorder).void_unwrap();
assert_eq!(diff, Value::Difference("0".into(), "1".into()));
}
}