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 = 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                    .then_some(SchemaFieldConfig::True)
144                    .unwrap_or_default();
145
146                Ok(config)
147            }
148            _ => Ok(SchemaFieldConfig::False),
149        }
150    }
151
152    let DeriveInput { attrs, .. } = syn::parse(input)?;
153    let metas = attrs.iter().map(|Attribute { meta, .. }| meta);
154    eval_metas(metas)
155}
156
157/// Enum controlling when we should emit a `#[schemars]` field attribute.
158pub(crate) type SchemaFieldConfig = LazyBool<SchemaFieldCondition>;
159
160impl From<Meta> for SchemaFieldConfig {
161    fn from(meta: Meta) -> Self {
162        Self::Lazy(meta.into())
163    }
164}
165
166#[derive(Clone, Debug, Eq, Hash, PartialEq)]
167pub(crate) struct SchemaFieldCondition(pub(crate) Meta);
168
169impl BitAnd for SchemaFieldCondition {
170    type Output = Self;
171
172    fn bitand(self, Self(rhs): Self) -> Self::Output {
173        let Self(lhs) = self;
174        Self(parse_quote!(all(#lhs, #rhs)))
175    }
176}
177
178impl BitAnd<&SchemaFieldCondition> for SchemaFieldCondition {
179    type Output = Self;
180
181    fn bitand(self, Self(rhs): &Self) -> Self::Output {
182        let Self(lhs) = self;
183        Self(parse_quote!(all(#lhs, #rhs)))
184    }
185}
186
187impl BitOr for SchemaFieldCondition {
188    type Output = Self;
189
190    fn bitor(self, Self(rhs): Self) -> Self::Output {
191        let Self(lhs) = self;
192        Self(parse_quote!(any(#lhs, #rhs)))
193    }
194}
195
196impl Not for SchemaFieldCondition {
197    type Output = Self;
198
199    fn not(self) -> Self::Output {
200        let Self(condition) = self;
201        Self(parse_quote!(not(#condition)))
202    }
203}
204
205impl From<Meta> for SchemaFieldCondition {
206    fn from(meta: Meta) -> Self {
207        Self(meta)
208    }
209}
210
211/// Get a `#[cfg]` expression under which this field has a `#[schemars]` attribute
212/// with a `with = ...` argument.
213pub(crate) fn schemars_with_attr_if(
214    attrs: &[Attribute],
215    filter: &[&str],
216) -> syn::Result<SchemaFieldConfig> {
217    fn eval_metas<'a>(
218        filter: &[&str],
219        metas: impl IntoIterator<Item = &'a Meta>,
220    ) -> syn::Result<SchemaFieldConfig> {
221        metas
222            .into_iter()
223            .map(|meta| eval_meta(filter, meta))
224            .try_fold(
225                SchemaFieldConfig::False,
226                |state, result| Ok(state | result?),
227            )
228    }
229
230    fn eval_meta(filter: &[&str], meta: &Meta) -> syn::Result<SchemaFieldConfig> {
231        match meta.path() {
232            path if path.is_ident("cfg_attr") => {
233                let CfgAttr {
234                    condition, metas, ..
235                } = meta.require_list()?.parse_args()?;
236
237                Ok(eval_metas(filter, &metas)? & SchemaFieldConfig::from(condition))
238            }
239            path if path.is_ident("schemars") => {
240                let config = meta
241                    .require_list()?
242                    .parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated)?
243                    .into_iter()
244                    .any(|meta| match meta.path().get_ident() {
245                        Some(ident) => filter.iter().any(|relevant| ident == relevant),
246                        _ => false,
247                    })
248                    .then_some(SchemaFieldConfig::True)
249                    .unwrap_or_default();
250                Ok(config)
251            }
252            _ => Ok(SchemaFieldConfig::False),
253        }
254    }
255
256    let metas = attrs.iter().map(|Attribute { meta, .. }| meta);
257    eval_metas(filter, metas)
258}