bon_macros/builder/builder_gen/member/config/
mod.rsmod blanket;
mod getter;
mod setters;
mod with;
pub(crate) use blanket::*;
pub(crate) use getter::*;
pub(crate) use setters::*;
pub(crate) use with::*;
use super::MemberOrigin;
use crate::parsing::SpannedKey;
use crate::util::prelude::*;
use std::fmt;
#[derive(Debug, darling::FromAttributes)]
#[darling(attributes(builder))]
pub(crate) struct MemberConfig {
#[darling(with = parse_optional_expr, map = Some)]
pub(crate) default: Option<SpannedKey<Option<syn::Expr>>>,
#[darling(with = parse_optional_expr, map = Some)]
pub(crate) field: Option<SpannedKey<Option<syn::Expr>>>,
pub(crate) getter: Option<SpannedKey<GetterConfig>>,
pub(crate) finish_fn: darling::util::Flag,
pub(crate) into: darling::util::Flag,
pub(crate) name: Option<syn::Ident>,
pub(crate) overwritable: darling::util::Flag,
pub(crate) required: darling::util::Flag,
#[darling(with = crate::parsing::parse_non_empty_paren_meta_list)]
pub(crate) setters: Option<SettersConfig>,
#[darling(with = parse_optional_expr, map = Some)]
pub(crate) skip: Option<SpannedKey<Option<syn::Expr>>>,
pub(crate) start_fn: darling::util::Flag,
pub(crate) with: Option<SpannedKey<WithConfig>>,
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum ParamName {
Default,
Field,
Getter,
FinishFn,
Into,
Name,
Overwritable,
Required,
Setters,
Skip,
StartFn,
With,
}
impl fmt::Display for ParamName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let str = match self {
Self::Default => "default",
Self::Field => "field",
Self::Getter => "getter",
Self::FinishFn => "finish_fn",
Self::Into => "into",
Self::Name => "name",
Self::Overwritable => "overwritable",
Self::Required => "required",
Self::Setters => "setters",
Self::Skip => "skip",
Self::StartFn => "start_fn",
Self::With => "with",
};
f.write_str(str)
}
}
impl MemberConfig {
fn validate_mutually_exclusive(
&self,
attr_name: ParamName,
attr_span: Span,
mutually_exclusive: &[ParamName],
) -> Result<()> {
self.validate_compat(attr_name, attr_span, mutually_exclusive, true)
}
fn validate_mutually_allowed(
&self,
attr_name: ParamName,
attr_span: Span,
mutually_allowed: &[ParamName],
) -> Result<()> {
self.validate_compat(attr_name, attr_span, mutually_allowed, false)
}
fn validate_compat(
&self,
attr_name: ParamName,
attr_span: Span,
patterns: &[ParamName],
mutually_exclusive: bool,
) -> Result<()> {
let conflicting: Vec<_> = self
.specified_param_names()
.filter(|name| *name != attr_name && patterns.contains(name) == mutually_exclusive)
.collect();
if conflicting.is_empty() {
return Ok(());
}
let conflicting = conflicting
.iter()
.map(|name| format!("`{name}`"))
.join(", ");
bail!(
&attr_span,
"`{attr_name}` attribute can't be specified together with {conflicting}",
);
}
fn specified_param_names(&self) -> impl Iterator<Item = ParamName> {
let Self {
default,
field,
getter,
finish_fn,
into,
name,
overwritable,
required,
setters,
skip,
start_fn,
with,
} = self;
let attrs = [
(default.is_some(), ParamName::Default),
(field.is_some(), ParamName::Field),
(getter.is_some(), ParamName::Getter),
(finish_fn.is_present(), ParamName::FinishFn),
(into.is_present(), ParamName::Into),
(name.is_some(), ParamName::Name),
(overwritable.is_present(), ParamName::Overwritable),
(required.is_present(), ParamName::Required),
(setters.is_some(), ParamName::Setters),
(skip.is_some(), ParamName::Skip),
(start_fn.is_present(), ParamName::StartFn),
(with.is_some(), ParamName::With),
];
attrs
.into_iter()
.filter(|(is_present, _)| *is_present)
.map(|(_, name)| name)
}
pub(crate) fn validate(&self, origin: MemberOrigin) -> Result {
if !cfg!(feature = "experimental-overwritable") && self.overwritable.is_present() {
bail!(
&self.overwritable.span(),
"🔬 `overwritable` attribute is experimental and requires \
\"experimental-overwritable\" cargo feature to be enabled; \
we would be glad to make this attribute stable if you find it useful; \
please leave a 👍 reaction under the issue https://github.com/elastio/bon/issues/149 \
to help us measure the demand for this feature; it would be \
double-awesome if you could also describe your use case in \
a comment under the issue for us to understand how it's used \
in practice",
);
}
if let Some(getter) = &self.getter {
if !cfg!(feature = "experimental-getter") {
bail!(
&getter.key,
"🔬 `getter` attribute is experimental and requires \
\"experimental-getter\" cargo feature to be enabled; \
if you find the current design of this attribute already \
solid please leave a 👍 reaction under the issue \
https://github.com/elastio/bon/issues/225; if you have \
any feedback, then feel free to leave a comment under that issue",
);
}
self.validate_mutually_exclusive(
ParamName::Getter,
getter.key.span(),
&[ParamName::Overwritable],
)?;
}
if self.start_fn.is_present() {
self.validate_mutually_allowed(
ParamName::StartFn,
self.start_fn.span(),
&[ParamName::Into],
)?;
}
if self.finish_fn.is_present() {
self.validate_mutually_allowed(
ParamName::FinishFn,
self.finish_fn.span(),
&[ParamName::Into],
)?;
}
if let Some(field) = &self.field {
self.validate_mutually_allowed(ParamName::Field, field.key.span(), &[])?;
}
if let Some(skip) = &self.skip {
match origin {
MemberOrigin::FnArg => {
bail!(
&skip.key.span(),
"`skip` attribute is not supported on function arguments; \
use a local variable instead.",
);
}
MemberOrigin::StructField => {}
}
if let Some(Some(_expr)) = self.default.as_deref() {
bail!(
&skip.key.span(),
"`skip` attribute can't be specified with the `default` attribute; \
if you wanted to specify a value for the member, then use \
the following syntax instead `#[builder(skip = value)]`",
);
}
self.validate_mutually_allowed(ParamName::Skip, skip.key.span(), &[])?;
}
if let Some(with) = &self.with {
self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?;
}
Ok(())
}
}
fn parse_optional_expr(meta: &syn::Meta) -> Result<SpannedKey<Option<syn::Expr>>> {
match meta {
syn::Meta::Path(path) => SpannedKey::new(path, None),
syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
syn::Meta::NameValue(meta) => SpannedKey::new(&meta.path, Some(meta.value.clone())),
}
}