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(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(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: Token![,],
90    metas: Punctuated<Meta, Token![,]>,
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, Token![,]>> {
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(Clone, Debug, Eq, Hash, 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(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(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(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(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, Token![,]>>::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}