1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Parameters of builder pattern and builder object.
use crate::{
set_literals, FXAttributes, FXBool, FXDoc, FXOrig, FXProp, FXPunctuated, FXSetState, FXString, FXSynValue,
FXTriggerHelper, FXTryInto, FromNestAttr,
};
use darling::{util::Flag, FromMeta};
use fieldx_derive_support::fxhelper;
use getset::Getters;
use syn::Token;
// TODO try to issue warnings with `diagnostics` for sub-arguments which are not supported at struct or field level.
/// Implementation of builder argument.
#[fxhelper(validate = Self::validate)]
#[derive(Debug, Default, Getters)]
pub struct FXBuilderHelper<const STRUCT: bool = false> {
#[getset(skip)]
attributes: Option<FXAttributes>,
#[getset(skip)]
attributes_impl: Option<FXAttributes>,
/// If set then builder setter methods must use the [`Into`] trait to coerce their arguments when possible.
/// This should make both of the following allowed:
///
/// ```ignore
/// let f1 = Foo::builder().comment("comment 1").build()?;
/// let f2 = Foo::builder().comment(String::from("comment 2")).build()?;
/// ```
#[getset(get = "pub")]
into: Option<FXBool>,
/// Wether builder is required or optional. In `fieldx` it means that for `required` optional fields user must
/// anyway always provide a value:
///
/// ```ignore
/// #[fxstruct(builder)]
/// struct Foo {
/// #[fieldx(optional, builder(required))]
/// comment: String,
/// }
///
/// let foo = Foo::builder().build()?; // Error because the `comment` is left unset
/// ```
#[getset(get = "pub")]
required: Option<FXBool>,
/// Means that by default a field doesn't get a builder unless explicitly specified. Only makes sense at struct
/// level and when some builder parameters need to be set but we don't want all non-optional fields to get a builder
/// method by default.
opt_in: Option<FXBool>,
/// Name of the method that would be invoked right after builder constructs the object and before it's returned to
/// the calling code.
post_build: Option<FXSynValue<syn::Ident, true>>,
/// If we want a fallible `post_build` then this is where its error type is defined. If two path's are given then
/// the second one must be a variant of the error enum that builder will use to report unset field.
#[getset(get = "pub")]
error: Option<FXSynValue<FXPunctuated<syn::Path, Token![,], 1, 2>>>,
/// Prefix for the builder setter methods.
prefix: Option<FXString>,
/// Documentation for the builder method.
method_doc: Option<FXDoc>,
}
impl<const STRUCT: bool> FXBuilderHelper<STRUCT> {
/// Shortcut to the `into` parameter.
///
/// Since it makes sense at both struct and field level `Option` is returned to know exactly if it is set or not.
#[inline]
pub fn is_into(&self) -> Option<FXProp<bool>> {
self.into.as_ref().map(|i| i.into())
}
/// Shortcut to the `required` parameter.
///
/// Since it makes sense at both struct and field level `Option` is returned to know exactly if it is set or not.
#[inline]
pub fn is_required(&self) -> Option<FXProp<bool>> {
self.required.as_ref().map(|r| r.into())
}
/// Shortcut to `post_build` parameter.
pub fn has_post_build(&self) -> FXProp<bool> {
self.post_build
.as_ref()
.map_or_else(|| FXProp::new(false, None), |pb| FXProp::new(true, pb.orig_span()))
}
/// Accessor for `attributes_impl`.
#[inline]
pub fn attributes(&self) -> Option<&FXAttributes> {
self.attributes.as_ref()
}
/// Accessor for `attributes_impl`.
#[inline]
pub fn attributes_impl(&self) -> Option<&FXAttributes> {
self.attributes_impl.as_ref()
}
/// The final error type.
pub fn error_type(&self) -> Option<&syn::Path> {
self.error().as_ref().and_then(|ev| ev.items().first())
}
/// The final error enum variant.
#[inline]
pub fn error_variant(&self) -> Option<&syn::Path> {
self.error().as_ref().and_then(|ev| ev.items().get(1))
}
#[inline]
pub fn method_doc(&self) -> Option<&FXDoc> {
self.method_doc.as_ref()
}
#[inline]
pub fn post_build(&self) -> Option<&FXSynValue<syn::Ident, true>> {
self.post_build.as_ref()
}
#[inline]
pub fn prefix(&self) -> Option<&FXString> {
self.prefix.as_ref()
}
#[inline]
pub fn opt_in(&self) -> Option<&FXBool> {
self.opt_in.as_ref()
}
#[doc(hidden)]
pub fn validate(&self) -> darling::Result<()> {
if !STRUCT {
if self.error.is_some() {
return Err(
darling::Error::custom(format!("parameter 'error' is only supported at struct level"))
.with_span(&self.error.final_span()),
);
}
if self.post_build.is_some() {
return Err(darling::Error::custom(format!(
"parameter 'post_build' is only supported at struct level"
))
.with_span(&self.post_build.final_span()));
}
if self.opt_in.is_some() {
return Err(
darling::Error::custom(format!("parameter 'opt_in' is only supported at struct level"))
.with_span(&self.opt_in.final_span()),
);
}
}
Ok(())
}
}
impl<const STRUCT: bool> FromNestAttr for FXBuilderHelper<STRUCT> {
set_literals! {builder, ..1 => name as Lit::Str}
fn for_keyword(_path: &syn::Path) -> darling::Result<Self> {
Ok(Self::default())
}
}