Skip to content

Commit 5726a52

Browse files
authored
feat: add support for #[builder(default)] attribute without expression (#10)
* enhance/default * update readme * update version
1 parent 74d6be6 commit 5726a52

File tree

9 files changed

+318
-58
lines changed

9 files changed

+318
-58
lines changed

README.md

Lines changed: 113 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ let user = UserBuilder::new()
5252
### Type-Level Constraint System
5353
- **Required Fields** - Completely prevent missing required field configuration
5454
- **Optional Fields** - Freely configurable fields
55-
- **Default Values** - Fields with intelligent default values using any Rust expression
55+
- **Default Values** - Fields with intelligent default values using `Default::default()` or custom expressions
5656
- **Conditional Requirements** - Express dynamic dependencies at the type level
5757
- **Complex Logic** - Support for AND/OR/NOT operators in complex conditional expressions
5858
- **Into Conversion** - Ergonomic setters with automatic type conversion via `Into<T>`
@@ -81,13 +81,15 @@ struct User {
8181
age: Option<u32>,
8282
#[builder(default = "String::from(\"[email protected]\")")]
8383
email: String,
84+
#[builder(default)]
85+
active: bool,
8486
}
8587

8688
// Type-safe builder pattern
8789
let user = UserBuilder::new()
8890
.with_name("Alice".to_string())
8991
.with_age(30)
90-
.build(); // email will be "[email protected]"
92+
.build(); // email will be "[email protected]", active will be false
9193
```
9294

9395
## Advanced Features
@@ -108,7 +110,7 @@ struct Account {
108110
// ✅ Compiles successfully
109111
let account1 = AccountBuilder::new().build();
110112

111-
// ✅ Compiles successfully
113+
// ✅ Compiles successfully
112114
let account2 = AccountBuilder::new()
113115
.with_email("[email protected]".to_string())
114116
.with_email_verified(true)
@@ -153,23 +155,23 @@ use typesafe_builder::*;
153155
struct ApiClient {
154156
#[builder(optional)]
155157
use_auth: Option<bool>,
156-
#[builder(optional)]
158+
#[builder(optional)]
157159
use_https: Option<bool>,
158160
#[builder(optional)]
159161
api_key: Option<String>,
160-
162+
161163
// Secret is required if using auth OR HTTPS
162164
#[builder(required_if = "use_auth || use_https")]
163165
secret: Option<String>,
164-
166+
165167
// Certificate is required only when using both auth AND HTTPS
166168
#[builder(required_if = "use_auth && use_https")]
167169
certificate: Option<String>,
168-
170+
169171
// Warning is required when using neither auth NOR HTTPS
170172
#[builder(required_if = "!use_auth && !use_https")]
171173
insecure_warning: Option<String>,
172-
174+
173175
// Complex condition: Token required when (auth OR HTTPS) AND (no API key)
174176
#[builder(required_if = "(use_auth || use_https) && !api_key")]
175177
fallback_token: Option<String>,
@@ -201,26 +203,72 @@ let client3 = ApiClientBuilder::new()
201203

202204
### 4. Default Values
203205

206+
TypeSafe Builder supports two ways to specify default values:
207+
208+
#### Simple Default Values (using `Default::default()`)
209+
210+
```rust
211+
use typesafe_builder::*;
212+
213+
#[derive(Builder)]
214+
struct Config {
215+
// Uses String::default() (empty string)
216+
#[builder(default)]
217+
name: String,
218+
219+
// Uses i32::default() (0)
220+
#[builder(default)]
221+
port: i32,
222+
223+
// Uses bool::default() (false)
224+
#[builder(default)]
225+
enabled: bool,
226+
227+
// Uses Vec::default() (empty vector)
228+
#[builder(default)]
229+
items: Vec<String>,
230+
231+
// Uses HashMap::default() (empty map)
232+
#[builder(default)]
233+
metadata: std::collections::HashMap<String, String>,
234+
235+
// Works with custom types that implement Default
236+
#[builder(default)]
237+
custom_field: MyCustomType,
238+
239+
#[builder(required)]
240+
service_name: String,
241+
}
242+
243+
// ✅ Use default values
244+
let config = ConfigBuilder::new()
245+
.with_service_name("my-service".to_string())
246+
.build();
247+
// name: "", port: 0, enabled: false, items: [], metadata: {}, custom_field: MyCustomType::default()
248+
```
249+
250+
#### Custom Default Expressions
251+
204252
```rust
205253
use typesafe_builder::*;
206254

207255
#[derive(Builder)]
208256
struct ServerConfig {
209257
#[builder(default = "String::from(\"localhost\")")]
210258
host: String,
211-
259+
212260
#[builder(default = "8080")]
213261
port: u16,
214-
262+
215263
#[builder(default = "vec![\"GET\".to_string(), \"POST\".to_string()]")]
216264
allowed_methods: Vec<String>,
217-
265+
218266
#[builder(default = "std::collections::HashMap::new()")]
219267
headers: std::collections::HashMap<String, String>,
220-
268+
221269
#[builder(required)]
222270
service_name: String,
223-
271+
224272
#[builder(optional)]
225273
ssl_cert: Option<String>,
226274
}
@@ -244,21 +292,51 @@ let config2 = ServerConfigBuilder::new()
244292
struct AppConfig {
245293
#[builder(default = "std::env::var(\"APP_NAME\").unwrap_or_else(|_| \"default-app\".to_string())")]
246294
app_name: String,
247-
295+
248296
#[builder(default = "chrono::Utc::now()")]
249297
created_at: chrono::DateTime<chrono::Utc>,
250-
298+
251299
#[builder(default = "uuid::Uuid::new_v4()")]
252300
instance_id: uuid::Uuid,
253301
}
254302
```
255303

304+
#### Mixed Default Types
305+
306+
```rust
307+
use typesafe_builder::*;
308+
309+
#[derive(Builder)]
310+
struct MixedConfig {
311+
// Simple default (uses Default::default())
312+
#[builder(default)]
313+
name: String,
314+
315+
// Custom expression default
316+
#[builder(default = "42")]
317+
port: i32,
318+
319+
// Simple default for collections
320+
#[builder(default)]
321+
tags: Vec<String>,
322+
323+
// Custom expression for complex initialization
324+
#[builder(default = "std::collections::HashMap::from([(\"key\".to_string(), \"value\".to_string())])")]
325+
metadata: std::collections::HashMap<String, String>,
326+
}
327+
328+
let config = MixedConfigBuilder::new().build();
329+
// name: "", port: 42, tags: [], metadata: {"key": "value"}
330+
```
331+
256332
Key features of default values:
257-
- Flexible expressions: Use any valid Rust expression as default value
258-
- No type restrictions: Works with primitives, collections, function calls, etc.
259-
- Environment variables: Access environment variables at build time
260-
- Function calls: Call any function or method as default value
261-
- Standalone attribute: Cannot be combined with `required`, `optional`, etc.
333+
- **Simple defaults**: Use `#[builder(default)]` for types implementing `Default`
334+
- **Custom expressions**: Use `#[builder(default = "expression")]` for any valid Rust expression
335+
- **No type restrictions**: Works with primitives, collections, function calls, etc.
336+
- **Environment variables**: Access environment variables at build time (custom expressions)
337+
- **Function calls**: Call any function or method as default value (custom expressions)
338+
- **Standalone attribute**: Cannot be combined with `required`, `optional`, etc.
339+
- **Zero runtime cost**: All defaults are computed at build time
262340

263341
### 5. Negation Operator Support
264342

@@ -269,7 +347,7 @@ use typesafe_builder::*;
269347
struct Database {
270348
#[builder(optional)]
271349
use_ssl: Option<bool>,
272-
350+
273351
// Warning message required when NOT using SSL
274352
#[builder(required_if = "!use_ssl")]
275353
warning_message: Option<String>,
@@ -294,7 +372,7 @@ struct User {
294372
#[builder(required)]
295373
#[builder(into)]
296374
name: String,
297-
375+
298376
#[builder(optional)]
299377
#[builder(into)]
300378
email: Option<String>,
@@ -349,7 +427,7 @@ struct User {
349427

350428
// ❌ Compile error
351429
let user = UserBuilder::new().build();
352-
// ^^^^^
430+
// ^^^^^
353431
// error: no method named `build` found for struct `UserBuilder<_TypesafeBuilderEmpty>`
354432
// method `build` is available on `UserBuilder<_TypesafeBuilderFilled>`
355433
```
@@ -369,7 +447,7 @@ struct Config {
369447
let config = ConfigBuilder::new()
370448
.with_feature(true)
371449
.build();
372-
// ^^^^^
450+
// ^^^^^
373451
// error: no method named `build` found for struct `ConfigBuilder<_TypesafeBuilderFilled, _TypesafeBuilderEmpty>`
374452
// method `build` is available on `ConfigBuilder<_TypesafeBuilderFilled, _TypesafeBuilderFilled>`
375453
```
@@ -383,19 +461,19 @@ let config = ConfigBuilder::new()
383461
struct ApiConfig {
384462
#[builder(required)]
385463
base_url: String,
386-
464+
387465
#[builder(optional)]
388466
use_auth: Option<bool>,
389-
467+
390468
#[builder(required_if = "use_auth")]
391469
api_key: Option<String>,
392-
470+
393471
#[builder(required_if = "use_auth")]
394472
secret: Option<String>,
395-
473+
396474
#[builder(default = "30")]
397475
timeout_seconds: u64,
398-
476+
399477
#[builder(default = "String::from(\"application/json\")")]
400478
content_type: String,
401479
}
@@ -408,22 +486,22 @@ struct ApiConfig {
408486
struct DatabaseConfig {
409487
#[builder(required)]
410488
host: String,
411-
489+
412490
#[builder(required)]
413491
database: String,
414-
492+
415493
#[builder(default = "5432")]
416494
port: u16,
417-
495+
418496
#[builder(default = "10")]
419497
max_connections: u32,
420-
498+
421499
#[builder(optional)]
422500
use_ssl: Option<bool>,
423-
501+
424502
#[builder(required_if = "use_ssl")]
425503
ssl_cert_path: Option<String>,
426-
504+
427505
#[builder(optional_if = "!use_ssl")]
428506
allow_insecure: Option<bool>,
429507
}

typesafe_builder_derive/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typesafe_builder_derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "typesafe_builder_derive"
3-
version = "1.5.0"
3+
version = "1.6.0"
44
edition = "2024"
55
authors = ["tomoikey"]
66
readme = "README.md"

0 commit comments

Comments
 (0)