square_ox/builder/
mod.rs

1use crate::errors::{BuildError, ValidationError};
2pub mod implementations;
3
4// Any Object that is buildable implements this trait
5// When implemented it allows the use of the object with the Builder::from() method
6pub trait Validate {
7    fn validate(self) -> Result<Self, ValidationError> where Self: Sized;
8}
9
10// When implemented, a builder holding the type the trait is implemented on can release a builder
11// holding the T type.
12// Allows the use of the .sub_builder_from() method.
13pub trait AddField<T> {
14    fn add_field(&mut self, field: T);
15}
16
17// This trait allows a builder to release a sub builder and allows that sub builder to add its field
18// to the releasing builders body.
19pub trait BackIntoBuilder<T: crate::builder::Validate, U: ParentBuilder + BackIntoBuilder<T, U>> {
20    fn add_field(self, field: T) -> Self;
21    fn sub_builder_from(self, body: T) -> Builder<T, U>;
22}
23
24// The builder struct holds a body of type T and a parent_builder of type U, where T implements
25// Validate and U implements ParentBuilder
26pub struct Builder<T, U>
27    where T: Validate,
28          U: ParentBuilder
29{
30    pub(crate) body: T,
31    pub(crate) parent_builder: Option<U>
32}
33
34pub struct Nil;
35
36// the ParentBuilder trait allows types to be placed within a builder's parent_builder field
37pub trait ParentBuilder {}
38
39// both Nil and Builder<T, U> where T implements validate and U implements ParentBuilder implement
40// the ParentBuilder trait automatically
41impl ParentBuilder for Nil {}
42
43impl<T: Validate, U: ParentBuilder> ParentBuilder for Builder<T, U> {}
44
45pub trait Buildable<T> {
46    fn build(self) -> Result<T, BuildError>;
47}
48
49// gives builders the ability to validate and build the objects they hold in their body field.
50impl<T: Validate> Buildable<T> for Builder<T, Nil> {
51    fn build(self) -> Result<T, BuildError> {
52        match self.body.validate() {
53            Ok(body) => Ok(body),
54            Err(_) => Err(BuildError)
55        }
56    }
57}
58
59// Allows a builder that holds a parent builder that implements the BackIntoBuilder trait to return
60// the builder it is holding while also validating and adding its content to the body of the parent
61// builder.
62impl<T: Validate, V: ParentBuilder + BackIntoBuilder<T, V>> Buildable<V> for Builder<T, V>  {
63    fn build(self) -> Result<V, BuildError> {
64        match self.body.validate() {
65            Ok(body) => {
66                Ok(self.parent_builder.unwrap().add_field(body))
67            },
68            Err(_) => Err(BuildError)
69        }
70    }
71}
72
73// The BackIntoBuilder trait is automatically implemented on any Builder with body of type T and a
74// Parent Builder whose body V implements the AddField trait for type T
75impl<V: AddField<T> + Validate, U: ParentBuilder, T: Validate> BackIntoBuilder<T, Builder<V, U>> for Builder<V, U> {
76    fn add_field(mut self, field: T) -> Self {
77        self.body.add_field(field);
78
79        self
80    }
81
82    fn sub_builder_from(self, body: T) -> Builder<T, Builder<V, U>> {
83        Builder {
84            body,
85            parent_builder: Some(self),
86        }
87    }
88}
89
90// Any type T that implements the Validate trait can be used in the Builder::from() method to return
91// a builder of type Builder<T, Nil>
92impl<T: Validate> From<T> for Builder<T, Nil> {
93    fn from(body: T) -> Self {
94        Builder {
95            body,
96            parent_builder: None::<Nil>
97        }
98    }
99}
100
101#[cfg(test)]
102mod builder_tests {
103    use super::*;
104    use square_ox_derive::Builder;
105
106    #[tokio::test]
107    async fn test_derive() {
108        #[derive(Default, Debug, Builder)]
109        struct Example {
110            #[builder_into]
111            field_0: String,
112            field_1: Option<i32>,
113            #[builder_into]
114            field_2: Option<String>
115        }
116
117        let sut = Builder::from(Example::default())
118            .field_0("a real String".to_string())
119            .field_1(54)
120            .field_2("just a &str")
121            .build();
122
123        assert!(sut.is_ok())
124    }
125}