如何创建使用流利链接语法而不需要括号的类型?

时间:2019-01-15 22:40:49

标签: rust fluent assertion

我正在尝试创建一个用于在Rust中测试的断言库。目前,我有类似的声明:

expect(value).to().be().equal_to(4);

将括号放在填充器tobe函数上,使其类似于:

expect(value).to.be.equal_to(4);

我认为这要求tobeexpectExpectation)返回的结构上的字段。当前看起来像这样:

struct Expectation<V: Debug> {
    value: V,
}

是否可以使它像这样:

struct Expectation<V: Debug> {
    value: V,
    to: Box<Expectation<V>>,
    be: Box<Expectation<V>>,
}

tobe指向它们所在的结构吗?

我已经尝试过了,但是构建起来很棘手。我什至不确定对象是否移动是安全的(也许可以通过Pin来防止?)。

我正在寻找任何允许上述expect(value).to.be语法的解决方案。

3 个答案:

答案 0 :(得分:4)

  

我正在寻找任何允许上述expect(value).to.be语法的解决方案。

然后保持简单

fn main() {
    expect(4).to.be.equal_to(3);
}

fn expect<T>(actual: T) -> To<T> {
    let be = Be {
        be: Expectation(actual),
    };
    To { to: be }
}

struct To<T> {
    pub to: Be<T>,
}

struct Be<T> {
    pub be: Expectation<T>,
}

struct Expectation<T>(T);

impl<T> Expectation<T> {
    fn equal_to<U>(&self, expected: U)
    where
        U: PartialEq<T>,
    {
        if expected != self.0 {
            panic!("Report error")
        }
    }
}
  

(可选)要跳过[tobe]

use std::{ops::Deref, rc::Rc};

fn main() {
    expect(4).to.be.equal_to(3);
    expect(4).to.equal_to(3);
    expect(4).equal_to(3);
}

fn expect<T>(actual: T) -> Expectation<T> {
    let core = Core(Rc::new(actual));

    let be = Be { core: core.clone() };

    let to = To {
        be,
        core: core.clone(),
    };

    Expectation {
        to,
        core: core.clone(),
    }
}

struct Expectation<T> {
    pub to: To<T>,
    core: Core<T>,
}

impl<T> Deref for Expectation<T> {
    type Target = Core<T>;
    fn deref(&self) -> &Core<T> {
        &self.core
    }
}

struct To<T> {
    pub be: Be<T>,
    core: Core<T>,
}

impl<T> Deref for To<T> {
    type Target = Core<T>;
    fn deref(&self) -> &Core<T> {
        &self.core
    }
}

struct Be<T> {
    core: Core<T>,
}

impl<T> Deref for Be<T> {
    type Target = Core<T>;
    fn deref(&self) -> &Core<T> {
        &self.core
    }
}

struct Core<T>(Rc<T>);

impl<T> Clone for Core<T> {
    fn clone(&self) -> Self {
        Core(self.0.clone())
    }
}

impl<T> Core<T> {
    fn equal_to<U>(&self, expected: U)
    where
        U: PartialEq<T>,
    {
        if expected != *self.0 {
            panic!("Report error")
        }
    }
}

一些宏会减少重复,但是我懒得实际显示;-)

在罗马时...

在设计测试断言库时,我会尝试发挥Rust的优势。对我来说,这意味着使用特征可以使人们轻松添加自定义断言。

use crate::testlib::prelude::*;

fn main() {
    expect(4).to(be.equal_to(3));
    expect(4).to(equal_to(3));
}

mod testlib {
    // Shorthand variants that will always be imported.
    // Minimize what's in here to avoid name collisions
    pub mod prelude {
        use super::*;

        pub fn expect<A>(actual: A) -> Expectation<A> {
            Expectation::new(actual)
        }

        #[allow(non_upper_case_globals)]
        pub static be: Be = Be;

        pub fn equal_to<E>(expected: E) -> EqualTo<E> {
            EqualTo::new(expected)
        }

    }

    // All the meat of the implementation. Can be divided up nicely.

    pub trait Assertion<A> {
        fn assert(&self, actual: &A);
    }

    pub struct Expectation<A>(A);
    impl<A> Expectation<A> {
        pub fn new(actual: A) -> Self {
            Expectation(actual)
        }

        pub fn to(&self, a: impl Assertion<A>) {
            a.assert(&self.0)
        }
    }

    pub struct Be;

    impl Be {
        pub fn equal_to<E>(&self, expected: E) -> EqualTo<E> {
            EqualTo::new(expected)
        }
    }

    pub struct EqualTo<E>(E);

    impl<E> EqualTo<E> {
        pub fn new(expected: E) -> Self {
            EqualTo(expected)
        }
    }

    impl<A, E> Assertion<A> for EqualTo<E>
    where
        A: PartialEq<E>,
    {
        fn assert(&self, actual: &A) {
            if *actual != self.0 {
                panic!("report an error")
            }
        }
    }
}

接下来要研究的步骤:

  • 断言可能应该向某些传入的结构报告失败,而不是惊慌。
  • 添加一个to_not和/或not_to否定匹配符。
  • 添加断言的组成部分。

答案 1 :(得分:2)

我在使用thunk板条箱懒惰地生成tobe方面取得了一些成功:

struct Expectation<V: Debug> {
    value: Rc<V>,
    to: Thunk<Box<Expectation<V>>>,
    be: Thunk<Box<Expectation<V>>>,
}

fn expect<V: Debug>(value: V) -> Expectation<V> {
    expect_rc(Rc::new(value))
}

fn expect_rc<V: Debug>(value: Rc<V>) -> Expectation<V> {
    let to_cloned = value.clone();
    let be_cloned = value.clone();
    Expectation {
        value,
        to: Thunk::defer(|| Box::new(expect_rc(to_cloned))),
        be: Thunk::defer(|| Box::new(expect_rc(be_cloned))),
    }
}

impl<V: PartialEq + Debug> Expectation<V> {
    fn equals<R: Debug>(&self, expected: R)
    where
        V: PartialEq<R> + Clone,
    {
        assert_eq!(self.value.deref().clone(), expected);
    }
}

由于Deref + Deref conversion的神奇之处,它可以按预期工作:

expect(4).to.be.equal_to(3);

答案 2 :(得分:1)

要设计自定义语法,我只使用一个宏:

macro_rules! expect {
    ($subject:expr, to, $($attr:tt)*) => {
        expect!($subject, $($attr)*)
    };
    ($subject:expr, be, $($attr:tt)*) => {
        expect!($subject, $($attr)*)
    };
    ($subject:expr, equal_to $object:expr) => {
        assert_eq!($subject, $object)
    };
}

expect!(1, to, be, equal_to 1);

仅仅为了获得特定的语法而部署框和自引用结构是过大的。

Link to playground