simple_builder/
lib.rs

1//! Simple-builder is a simple-as-possible implementation of the builder pattern for arbitrary Rust structs.
2//! It seeks to provide an easy to use interface for common use cases, such as web request with
3//! many optional parameters.
4//!
5//! ## Implementation
6//! The derived builders offer compile-time guarantees that no incorrect instance will be created
7//! without super-linear compile times since required parameters go directly in the `new` method of
8//! the builder.
9//!
10//! There are two downsides to this:
11//! - The derived builders have lengthy constructors for structs with many required parameters.
12//! This is arguably no longer a builder pattern, and as such isn't suitable for structs with many
13//! required parameters.
14//! - Calling `build` twice will result in a panic, since it owns and consumes the passed in data.
15//!
16//! Other implementations may suit your needs better if you have many _required_ parameters,
17//! need to clone your builders, or have strict requirements for compile-time type checking of
18//! required parameters and intermediate states.
19pub use simple_builder_macro::Builder;
20
21#[derive(Builder, Debug, PartialEq)]
22struct TestItemOptionals {
23    a: Option<String>,
24    b: Option<bool>,
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    #[derive(Builder, Debug, PartialEq)]
32    struct TestItem {
33        #[builder(required)]
34        a: i32,
35        #[builder(required)]
36        b: i32,
37    }
38
39    #[derive(Builder, Debug, PartialEq)]
40    struct TestItemOptionals {
41        a: Option<String>,
42        b: Option<bool>,
43    }
44
45    #[derive(Builder, Debug, PartialEq)]
46    struct TestItemMixedRequiredOptionals {
47        a: Option<String>,
48        b: Option<bool>,
49        #[builder(required)]
50        c: i32,
51    }
52
53    #[derive(Builder, Debug, PartialEq)]
54    struct TestItemMixedRequiredOptionalReferences<'t> {
55        a: Option<&'t str>,
56        #[builder(required)]
57        b: &'t str,
58    }
59
60    #[test]
61    fn test_developer_experience() {
62        let t = trybuild::TestCases::new();
63        t.compile_fail("tests/ui/*.rs");
64    }
65
66    #[test]
67    fn test_builder_method_on_original_struct() {
68        let expected_item = TestItem { a: 0, b: 1 };
69
70        let mut builder = TestItem::builder(0, 1);
71
72        let test_item = builder.build();
73
74        assert_eq!(expected_item, test_item);
75    }
76
77    #[test]
78    #[should_panic(
79        expected = "Option must be Some(T) for required fields. Builder may have already been consumed by calling `build`"
80    )]
81    fn test_builder_panic_on_second_build_call() {
82        let mut builder = TestItem::builder(0, 1);
83
84        let _first_build = builder.build();
85        let _second_build = builder.build();
86    }
87
88    #[test]
89    fn test_empty_builder_none_required() {
90        let expected_item = TestItem { a: 0, b: 1 };
91
92        let mut builder = TestItemBuilder::new(0, 1);
93
94        let test_item = builder.build();
95
96        assert_eq!(expected_item, test_item);
97    }
98
99    #[test]
100    fn test_builder_with_required() {
101        let expected_item = TestItemOptionals { a: None, b: None };
102
103        let mut builder = TestItemOptionals::builder();
104
105        let test_item = builder.build();
106
107        assert_eq!(expected_item, test_item);
108    }
109
110    #[test]
111    fn test_builder_optional_all_present() {
112        let expected_item = TestItemOptionals {
113            a: Some("string".to_string()),
114            b: Some(false),
115        };
116
117        let mut builder = TestItemOptionalsBuilder::new();
118
119        let item = builder.a("string".to_string()).b(false).build();
120
121        assert_eq!(expected_item, item);
122    }
123
124    #[test]
125    fn test_builder_optional_with_none() {
126        let expected_item = TestItemOptionals {
127            a: None,
128            b: Some(false),
129        };
130
131        let mut builder = TestItemOptionalsBuilder::new();
132
133        let item = builder.b(false).build();
134
135        assert_eq!(expected_item, item);
136    }
137
138    #[test]
139    fn test_builder_mixed_required_and_optional() {
140        let expected_item = TestItemMixedRequiredOptionals {
141            a: Some("string".to_string()),
142            b: None,
143            c: 42,
144        };
145
146        let mut builder = TestItemMixedRequiredOptionalsBuilder::new(42);
147
148        let item = builder.a("string".to_string()).build();
149
150        assert_eq!(expected_item, item);
151    }
152
153    #[test]
154    fn test_builder_mixed_required_and_optional_references() {
155        let expected_item = TestItemMixedRequiredOptionalReferences {
156            a: Some("a"),
157            b: "string",
158        };
159
160        let mut builder = TestItemMixedRequiredOptionalReferencesBuilder::new("string");
161
162        let item = builder.a("a").build();
163
164        assert_eq!(expected_item, item);
165    }
166
167    #[test]
168    fn test_builder_ownership() {
169        #[derive(Debug, PartialEq, Eq)]
170        struct Item {
171            field: i32,
172        }
173
174        #[derive(Builder, Debug, Eq, PartialEq)]
175        struct TestItem {
176            a: Option<Item>,
177            #[builder(required)]
178            b: Item,
179        }
180
181        let a = Item { field: 0 };
182        let a1 = Item { field: 0 };
183
184        let b = Item { field: 1 };
185        let b1 = Item { field: 1 };
186
187        let mut builder = TestItem::builder(b);
188
189        let item = builder.a(a).build();
190
191        let expected_item = TestItem { a: Some(a1), b: b1 };
192
193        assert_eq!(expected_item, item);
194    }
195
196    #[test]
197    fn test_builder_with_required_trait_and_lifetimes() {
198        trait Marker {}
199
200        #[derive(Debug, Eq, PartialEq, PartialOrd)]
201        struct GenericT {
202            num: i64,
203        }
204
205        impl Marker for GenericT {}
206
207        #[derive(Builder, Debug, Eq, PartialEq)]
208        struct TestItemGenericAddress<'t, 'u, T, U>
209        where
210            T: Marker,
211            U: Marker,
212        {
213            a: Option<&'t T>,
214            #[builder(required)]
215            b: &'u U,
216        }
217
218        let test_struct_t = GenericT { num: 42 };
219        let test_struct_u = GenericT { num: 1 };
220
221        let expected_item = TestItemGenericAddress {
222            a: Some(&test_struct_t),
223            b: &test_struct_u,
224        };
225
226        let mut builder = TestItemGenericAddress::builder(&test_struct_u);
227
228        let item = builder.a(&test_struct_t).build();
229
230        assert_eq!(expected_item, item);
231    }
232}