Skip to main content

serde_with_macros/
utils.rs

1use crate::lazy_bool::LazyBool;
2use darling::FromDeriveInput;
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::ToTokens;
6use std::ops::{BitAnd, BitOr, Not};
7use syn::{
8    parse::{Parse, ParseStream},
9    parse_quote,
10    punctuated::Punctuated,
11    Attribute, DeriveInput, Generics, Meta, Path, PathSegment, Token, TypeGenerics, WhereClause,
12};
13
14/// Merge multiple [`syn::Error`] into one.
15pub(crate) trait IteratorExt {
16    fn collect_error(self) -> syn::Result<()>
17    where
18        Self: Iterator<Item = syn::Result<()>> + Sized,
19    {
20        let accu = Ok(());
21        self.fold(accu, |accu, error| match (accu, error) {
22            (Ok(()), error) => error,
23            (accu, Ok(())) => accu,
24            (Err(mut err), Err(error)) => {
25                err.combine(error);
26                Err(err)
27            }
28        })
29    }
30}
31impl<I> IteratorExt for I where I: Iterator<Item = syn::Result<()>> + Sized {}
32
33/// Attributes usable for derive macros
34#[derive(#[automatically_derived]
#[allow(clippy :: manual_unwrap_or_default)]
impl ::darling::FromDeriveInput for DeriveOptions {
    fn from_derive_input(__di: &::darling::export::syn::DeriveInput)
        -> ::darling::Result<Self> {
        let mut __errors = ::darling::Error::accumulator();
        let mut alt_crate_path:
                (bool, ::darling::export::Option<Option<Path>>) =
            (false, None);
        use ::darling::ToTokens;
        for __attr in &__di.attrs {
            match ::darling::export::ToString::to_string(&__attr.path().clone().into_token_stream()).as_str()
                {
                "serde_with" => {
                    match ::darling::util::parse_attribute_to_meta_list(__attr)
                        {
                        ::darling::export::Ok(__data) => {
                            match ::darling::export::NestedMeta::parse_meta_list(__data.tokens)
                                {
                                ::darling::export::Ok(ref __items) => {
                                    if __items.is_empty() { continue; }
                                    for __item in __items {
                                        match *__item {
                                            ::darling::export::NestedMeta::Meta(ref __inner) => {
                                                let __name =
                                                    ::darling::util::path_to_string(__inner.path());
                                                match __name.as_str() {
                                                    "crate" => {
                                                        if !alt_crate_path.0 {
                                                            alt_crate_path =
                                                                (true,
                                                                    __errors.handle(::darling::export::identity::<fn(&::darling::export::syn::Meta)
                                                                                            ->
                                                                                                ::darling::Result<_>>(::darling::FromMeta::from_meta)(__inner).map_err(|e|
                                                                                e.with_span(&__inner).at("crate"))));
                                                        } else {
                                                            __errors.push(::darling::Error::duplicate_field("crate").with_span(&__inner));
                                                        }
                                                    }
                                                    __other => {
                                                        __errors.push(::darling::Error::unknown_field_with_alts(__other,
                                                                    &["crate"]).with_span(__inner));
                                                    }
                                                }
                                            }
                                            ::darling::export::NestedMeta::Lit(ref __inner) => {
                                                __errors.push(::darling::Error::unsupported_format("literal").with_span(__inner));
                                            }
                                        }
                                    }
                                }
                                ::darling::export::Err(__err) => {
                                    __errors.push(__err.into());
                                }
                            }
                        }
                        ::darling::export::Err(__err) => { __errors.push(__err); }
                    }
                }
                _ => continue,
            }
        }
        __errors.handle(::darling::export::Ok(&__di.data).and_then(::darling::export::Ok));
        __errors.finish()?;
        ::darling::export::Ok(DeriveOptions {
                alt_crate_path: if let Some(__val) = alt_crate_path.1 {
                    __val
                } else { ::darling::export::Default::default() },
            })
    }
}FromDeriveInput)]
35#[darling(attributes(serde_with))]
36pub(crate) struct DeriveOptions {
37    /// Path to the crate
38    #[darling(rename = "crate", default)]
39    pub(crate) alt_crate_path: Option<Path>,
40}
41
42impl DeriveOptions {
43    pub(crate) fn from_derive_input(input: &DeriveInput) -> Result<Self, TokenStream> {
44        match <Self as FromDeriveInput>::from_derive_input(input) {
45            Ok(v) => Ok(v),
46            Err(e) => Err(TokenStream::from(e.write_errors())),
47        }
48    }
49
50    pub(crate) fn get_serde_with_path(&self) -> Path {
51        self.alt_crate_path
52            .clone()
53            .unwrap_or_else(|| syn::parse_str("::serde_with").unwrap())
54    }
55}
56
57// Inspired by https://github.com/serde-rs/serde/blob/fb2fe409c8f7ad6c95e3096e5e9ede865c8cfb49/serde_derive/src/de.rs#L3120
58// Serde is also licensed Apache 2 + MIT
59pub(crate) fn split_with_de_lifetime(
60    generics: &Generics,
61) -> (DeImplGenerics<'_>, TypeGenerics<'_>, Option<&WhereClause>) {
62    let de_impl_generics = DeImplGenerics(generics);
63    let (_, ty_generics, where_clause) = generics.split_for_impl();
64    (de_impl_generics, ty_generics, where_clause)
65}
66
67pub(crate) struct DeImplGenerics<'a>(&'a Generics);
68
69impl ToTokens for DeImplGenerics<'_> {
70    fn to_tokens(&self, tokens: &mut TokenStream2) {
71        let mut generics = self.0.clone();
72        generics.params = Some(::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_lifetime(&mut _s, "\'de");
        _s
    })parse_quote!('de))
73            .into_iter()
74            .chain(generics.params)
75            .collect();
76        let (impl_generics, _, _) = generics.split_for_impl();
77        impl_generics.to_tokens(tokens);
78    }
79}
80
81/// Represents the macro body of a `#[cfg_attr]` attribute.
82///
83/// ```text
84/// #[cfg_attr(feature = "things", derive(Macro))]
85///            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86/// ```
87struct CfgAttr {
88    condition: Meta,
89    _comma: ::syn::token::CommaToken![,],
90    metas: Punctuated<Meta, ::syn::token::CommaToken![,]>,
91}
92
93impl Parse for CfgAttr {
94    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
95        Ok(Self {
96            condition: input.parse()?,
97            _comma: input.parse()?,
98            metas: Punctuated::parse_terminated(input)?,
99        })
100    }
101}
102
103/// Determine if there is a `#[derive(JsonSchema)]` on this struct.
104pub(crate) fn has_derive_jsonschema(input: TokenStream) -> syn::Result<SchemaFieldConfig> {
105    fn parse_derive_args(input: ParseStream<'_>) -> syn::Result<Punctuated<Path, ::syn::token::CommaToken![,]>> {
106        Punctuated::parse_terminated_with(input, Path::parse_mod_style)
107    }
108
109    fn eval_metas<'a>(metas: impl IntoIterator<Item = &'a Meta>) -> syn::Result<SchemaFieldConfig> {
110        metas
111            .into_iter()
112            .map(eval_meta)
113            .try_fold(
114                SchemaFieldConfig::False,
115                |state, result| Ok(state | result?),
116            )
117    }
118
119    fn eval_meta(meta: &Meta) -> syn::Result<SchemaFieldConfig> {
120        match meta.path() {
121            path if path.is_ident("cfg_attr") => {
122                let CfgAttr {
123                    condition, metas, ..
124                } = meta.require_list()?.parse_args()?;
125
126                Ok(eval_metas(&metas)? & SchemaFieldConfig::Lazy(condition.into()))
127            }
128            path if path.is_ident("derive") => {
129                let config = if meta
130                    .require_list()?
131                    .parse_args_with(parse_derive_args)?
132                    .into_iter()
133                    .any(|Path { segments, .. }| {
134                        // This matches `JsonSchema`, `schemars::JsonSchema`
135                        //   as well as any other path ending with `JsonSchema`.
136                        // This will not match aliased `JsonSchema`s,
137                        //   but might match other `JsonSchema` not `schemars::JsonSchema`!
138                        match segments.last() {
139                            Some(PathSegment { ident, .. }) => ident == "JsonSchema",
140                            _ => false,
141                        }
142                    }) {
143                    SchemaFieldConfig::True
144                } else {
145                    LazyBool::default()
146                };
147                Ok(config)
148            }
149            _ => Ok(SchemaFieldConfig::False),
150        }
151    }
152
153    let DeriveInput { attrs, .. } = syn::parse(input)?;
154    let metas = attrs.iter().map(|Attribute { meta, .. }| meta);
155    eval_metas(metas)
156}
157
158/// Enum controlling when we should emit a `#[schemars]` field attribute.
159pub(crate) type SchemaFieldConfig = LazyBool<SchemaFieldCondition>;
160
161impl From<Meta> for SchemaFieldConfig {
162    fn from(meta: Meta) -> Self {
163        Self::Lazy(meta.into())
164    }
165}
166
167#[derive(#[automatically_derived]
impl ::core::clone::Clone for SchemaFieldCondition {
    #[inline]
    fn clone(&self) -> SchemaFieldCondition {
        SchemaFieldCondition(::core::clone::Clone::clone(&self.0))
    }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for SchemaFieldCondition {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f,
            "SchemaFieldCondition", &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::cmp::Eq for SchemaFieldCondition {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Meta>;
    }
}Eq, #[automatically_derived]
impl ::core::hash::Hash for SchemaFieldCondition {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash, #[automatically_derived]
impl ::core::cmp::PartialEq for SchemaFieldCondition {
    #[inline]
    fn eq(&self, other: &SchemaFieldCondition) -> bool { self.0 == other.0 }
}PartialEq)]
168pub(crate) struct SchemaFieldCondition(pub(crate) Meta);
169
170impl BitAnd for SchemaFieldCondition {
171    type Output = Self;
172
173    fn bitand(self, Self(rhs): Self) -> Self::Output {
174        let Self(lhs) = self;
175        Self(::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_ident(&mut _s, "all");
        ::quote::__private::push_group(&mut _s,
            ::quote::__private::Delimiter::Parenthesis,
            {
                let mut _s = ::quote::__private::TokenStream::new();
                ::quote::ToTokens::to_tokens(&lhs, &mut _s);
                ::quote::__private::push_comma(&mut _s);
                ::quote::ToTokens::to_tokens(&rhs, &mut _s);
                _s
            });
        _s
    })parse_quote!(all(#lhs, #rhs)))
176    }
177}
178
179impl BitAnd<&SchemaFieldCondition> for SchemaFieldCondition {
180    type Output = Self;
181
182    fn bitand(self, Self(rhs): &Self) -> Self::Output {
183        let Self(lhs) = self;
184        Self(::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_ident(&mut _s, "all");
        ::quote::__private::push_group(&mut _s,
            ::quote::__private::Delimiter::Parenthesis,
            {
                let mut _s = ::quote::__private::TokenStream::new();
                ::quote::ToTokens::to_tokens(&lhs, &mut _s);
                ::quote::__private::push_comma(&mut _s);
                ::quote::ToTokens::to_tokens(&rhs, &mut _s);
                _s
            });
        _s
    })parse_quote!(all(#lhs, #rhs)))
185    }
186}
187
188impl BitOr for SchemaFieldCondition {
189    type Output = Self;
190
191    fn bitor(self, Self(rhs): Self) -> Self::Output {
192        let Self(lhs) = self;
193        Self(::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_ident(&mut _s, "any");
        ::quote::__private::push_group(&mut _s,
            ::quote::__private::Delimiter::Parenthesis,
            {
                let mut _s = ::quote::__private::TokenStream::new();
                ::quote::ToTokens::to_tokens(&lhs, &mut _s);
                ::quote::__private::push_comma(&mut _s);
                ::quote::ToTokens::to_tokens(&rhs, &mut _s);
                _s
            });
        _s
    })parse_quote!(any(#lhs, #rhs)))
194    }
195}
196
197impl Not for SchemaFieldCondition {
198    type Output = Self;
199
200    fn not(self) -> Self::Output {
201        let Self(condition) = self;
202        Self(::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_ident(&mut _s, "not");
        ::quote::__private::push_group(&mut _s,
            ::quote::__private::Delimiter::Parenthesis,
            {
                let mut _s = ::quote::__private::TokenStream::new();
                ::quote::ToTokens::to_tokens(&condition, &mut _s);
                _s
            });
        _s
    })parse_quote!(not(#condition)))
203    }
204}
205
206impl From<Meta> for SchemaFieldCondition {
207    fn from(meta: Meta) -> Self {
208        Self(meta)
209    }
210}
211
212/// Get a `#[cfg]` expression under which this field has a `#[schemars]` attribute
213/// with a `with = ...` argument.
214pub(crate) fn schemars_with_attr_if(
215    attrs: &[Attribute],
216    filter: &[&str],
217) -> syn::Result<SchemaFieldConfig> {
218    fn eval_metas<'a>(
219        filter: &[&str],
220        metas: impl IntoIterator<Item = &'a Meta>,
221    ) -> syn::Result<SchemaFieldConfig> {
222        metas
223            .into_iter()
224            .map(|meta| eval_meta(filter, meta))
225            .try_fold(
226                SchemaFieldConfig::False,
227                |state, result| Ok(state | result?),
228            )
229    }
230
231    fn eval_meta(filter: &[&str], meta: &Meta) -> syn::Result<SchemaFieldConfig> {
232        match meta.path() {
233            path if path.is_ident("cfg_attr") => {
234                let CfgAttr {
235                    condition, metas, ..
236                } = meta.require_list()?.parse_args()?;
237
238                Ok(eval_metas(filter, &metas)? & SchemaFieldConfig::from(condition))
239            }
240            path if path.is_ident("schemars") => {
241                let config = if meta
242                    .require_list()?
243                    .parse_args_with(<Punctuated<Meta, ::syn::token::CommaToken![,]>>::parse_terminated)?
244                    .into_iter()
245                    .any(|meta| match meta.path().get_ident() {
246                        Some(ident) => filter.iter().any(|relevant| ident == relevant),
247                        _ => false,
248                    }) {
249                    SchemaFieldConfig::True
250                } else {
251                    LazyBool::default()
252                };
253                Ok(config)
254            }
255            _ => Ok(SchemaFieldConfig::False),
256        }
257    }
258
259    let metas = attrs.iter().map(|Attribute { meta, .. }| meta);
260    eval_metas(filter, metas)
261}