Skip to main content

apache_avro_derive/attributes/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::case::RenameRule;
19use darling::{FromAttributes, FromMeta};
20use proc_macro2::{Span, TokenStream};
21use quote::quote;
22use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned};
23
24mod avro;
25mod serde;
26
27#[derive(Default)]
28pub struct NamedTypeOptions {
29    pub name: String,
30    pub doc: Option<String>,
31    pub aliases: Vec<String>,
32    pub rename_all: RenameRule,
33    pub transparent: bool,
34    pub default: TokenStream,
35}
36
37impl NamedTypeOptions {
38    pub fn new(
39        ident: &Ident,
40        attributes: &[Attribute],
41        span: Span,
42    ) -> Result<Self, Vec<syn::Error>> {
43        let avro =
44            avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
45        let serde =
46            serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
47
48        // Check for deprecated attributes
49        avro.deprecated(span);
50
51        // Collect errors so user gets all feedback at once
52        let mut errors = Vec::new();
53
54        // Check for any Serde attributes that are hard errors
55        if serde.tag.is_some()
56            || serde.content.is_some()
57            || serde.untagged
58            || serde.variant_identifier
59            || serde.field_identifier
60        {
61            errors.push(syn::Error::new(
62                span,
63                "AvroSchema derive does not support changing the tagging Serde generates (`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)",
64            ));
65        }
66        if serde.remote.is_some() {
67            errors.push(syn::Error::new(
68                span,
69                "AvroSchema derive does not support the Serde `remote` attribute",
70            ));
71        }
72        if serde.rename_all.deserialize != serde.rename_all.serialize {
73            errors.push(syn::Error::new(
74                span,
75                r#"AvroSchema derive does not support different rename rules for serializing and deserializing (`rename_all(serialize = "..", deserialize = "..")`)"#
76            ));
77        }
78
79        // Check for conflicts between Serde and Avro
80        if avro.name.is_some() && avro.name != serde.rename {
81            errors.push(syn::Error::new(
82                span,
83                r#"#[avro(name = "..")] must match #[serde(rename = "..")], it's also deprecated. Please use only `#[serde(rename = "..")]`"#,
84            ));
85        }
86        if avro.rename_all != RenameRule::None && serde.rename_all.serialize != avro.rename_all {
87            errors.push(syn::Error::new(
88                span,
89                r#"#[avro(rename_all = "..")] must match #[serde(rename_all = "..")], it's also deprecated. Please use only `#[serde(rename_all = "..")]`"#,
90            ));
91        }
92        if serde.transparent
93            && (serde.rename.is_some()
94                || avro.name.is_some()
95                || avro.namespace.is_some()
96                || avro.doc.is_some()
97                || !avro.alias.is_empty()
98                || avro.rename_all != RenameRule::None
99                || serde.rename_all.serialize != RenameRule::None
100                || serde.rename_all.deserialize != RenameRule::None)
101        {
102            errors.push(syn::Error::new(
103                span,
104                "AvroSchema: #[serde(transparent)] is incompatible with all other attributes",
105            ));
106        }
107
108        if !errors.is_empty() {
109            return Err(errors);
110        }
111
112        let name = serde.rename.unwrap_or(ident.to_string());
113        let full_schema_name = vec![avro.namespace, Some(name)]
114            .into_iter()
115            .flatten()
116            .collect::<Vec<String>>()
117            .join(".");
118
119        let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
120
121        let default = match avro.default {
122            None => quote! { None },
123            Some(default_value) => {
124                let _: serde_json::Value =
125                    serde_json::from_str(&default_value[..]).map_err(|e| {
126                        vec![syn::Error::new(
127                            ident.span(),
128                            format!("Invalid Avro `default` JSON: \n{e}"),
129                        )]
130                    })?;
131                quote! {
132                    Some(::serde_json::from_str(#default_value).expect(format!("Invalid JSON: {:?}", #default_value).as_str()))
133                }
134            }
135        };
136
137        Ok(Self {
138            name: full_schema_name,
139            doc,
140            aliases: avro.alias,
141            rename_all: serde.rename_all.serialize,
142            transparent: serde.transparent,
143            default,
144        })
145    }
146}
147
148pub struct VariantOptions {
149    pub rename: Option<String>,
150}
151
152impl VariantOptions {
153    pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, Vec<syn::Error>> {
154        let avro = avro::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
155        let serde =
156            serde::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
157
158        // Check for deprecated attributes
159        avro.deprecated(span);
160
161        // Collect errors so user gets all feedback at once
162        let mut errors = Vec::new();
163
164        // Check for any Serde attributes that are hard errors
165        if serde.other || serde.untagged {
166            errors.push(syn::Error::new(
167                span,
168                "AvroSchema derive does not support changing the tagging Serde generates (`other`, `untagged`)",
169            ));
170        }
171
172        // Check for conflicts between Serde and Avro
173        if avro.rename.is_some() && serde.rename != avro.rename {
174            errors.push(syn::Error::new(
175                span,
176                r#"`#[avro(rename = "..")]` must match `#[serde(rename = "..")]`, it's also deprecated. Please use only `#[serde(rename = "..")]`"#
177            ));
178        }
179
180        if !errors.is_empty() {
181            return Err(errors);
182        }
183
184        Ok(Self {
185            rename: serde.rename,
186        })
187    }
188}
189
190/// How to get the schema for this field or variant.
191#[derive(Debug, PartialEq, Default, Clone)]
192pub enum With {
193    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
194    #[default]
195    Trait,
196    /// Use `module::get_schema_in_ctxt` where the module is defined by Serde's `with` attribute.
197    Serde(Path),
198    /// Call the function in this expression.
199    Expr(Expr),
200}
201
202impl With {
203    fn from_avro_and_serde(
204        avro: &avro::With,
205        serde: Option<&String>,
206        span: Span,
207    ) -> Result<Self, syn::Error> {
208        match &avro {
209            avro::With::Trait => Ok(Self::Trait),
210            avro::With::Serde => {
211                if let Some(serde) = serde {
212                    let path = Path::from_string(serde).map_err(|err| {
213                        syn::Error::new(
214                            span,
215                            format!(
216                                r#"AvroSchema: Expected a path for `#[serde(with = "..")]`: {err:?}"#
217                            ),
218                        )
219                    })?;
220                    Ok(Self::Serde(path))
221                } else {
222                    Err(syn::Error::new(
223                        span,
224                        r#"`#[avro(with)]` requires `#[serde(with = "some_module")]` or provide a function to call `#[avro(with = some_fn)]`"#,
225                    ))
226                }
227            }
228            avro::With::Expr(expr) => Ok(Self::Expr(expr.clone())),
229        }
230    }
231}
232/// How to get the default value for a value.
233#[derive(Debug, PartialEq, Default)]
234pub enum FieldDefault {
235    /// Use `<T as AvroSchemaComponent>::field_default`.
236    #[default]
237    Trait,
238    /// Don't set a default.
239    Disabled,
240    /// Use this JSON value.
241    Value(String),
242}
243
244impl FromMeta for FieldDefault {
245    fn from_string(value: &str) -> darling::Result<Self> {
246        Ok(Self::Value(value.to_string()))
247    }
248
249    fn from_bool(value: bool) -> darling::Result<Self> {
250        if value {
251            Err(darling::Error::custom(
252                "Expected `false` or a JSON string, got `true`",
253            ))
254        } else {
255            Ok(Self::Disabled)
256        }
257    }
258}
259
260#[derive(Default)]
261pub struct FieldOptions {
262    pub doc: Option<String>,
263    pub default: FieldDefault,
264    pub alias: Vec<String>,
265    pub rename: Option<String>,
266    pub skip: bool,
267    pub flatten: bool,
268    pub with: With,
269}
270
271impl FieldOptions {
272    pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, Vec<syn::Error>> {
273        let mut avro =
274            avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
275        let mut serde =
276            serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
277        // Sort the aliases, so our check for equality does not fail if they are provided in a different order
278        avro.alias.sort();
279        serde.alias.sort();
280
281        // Check for deprecated attributes
282        avro.deprecated(span);
283
284        // Collect errors so user gets all feedback at once
285        let mut errors = Vec::new();
286
287        // Check for any Serde attributes that are hard errors
288        if serde.getter.is_some() {
289            errors.push(syn::Error::new(
290                span,
291                "AvroSchema derive does not support the Serde `getter` attribute",
292            ));
293        }
294
295        // Check for conflicts between Serde and Avro
296        if avro.skip && !(serde.skip || (serde.skip_serializing && serde.skip_deserializing)) {
297            errors.push(syn::Error::new(
298                span,
299                "`#[avro(skip)]` requires `#[serde(skip)]`, it's also deprecated. Please use only `#[serde(skip)]`"
300            ));
301        }
302        if avro.flatten && !serde.flatten {
303            errors.push(syn::Error::new(
304                span,
305                "`#[avro(flatten)]` requires `#[serde(flatten)]`, it's also deprecated. Please use only `#[serde(flatten)]`"
306            ));
307        }
308        // TODO: rename and alias checking can be relaxed with a more complex check, would require the field name
309        if avro.rename.is_some() && serde.rename != avro.rename {
310            errors.push(syn::Error::new(
311                span,
312                r#"`#[avro(rename = "..")]` must match `#[serde(rename = "..")]`, it's also deprecated. Please use only `#[serde(rename = "..")]`"#
313            ));
314        }
315        if !avro.alias.is_empty() && serde.alias != avro.alias {
316            errors.push(syn::Error::new(
317                span,
318                r#"`#[avro(alias = "..")]` must match `#[serde(alias = "..")]`, it's also deprecated. Please use only `#[serde(alias = "..")]`"#
319            ));
320        }
321
322        let with = match With::from_avro_and_serde(&avro.with, serde.with.as_ref(), span) {
323            Ok(with) => with,
324            Err(error) => {
325                errors.push(error);
326                // This won't actually be used, but it does simplify the code
327                With::Trait
328            }
329        };
330        // TODO: Implement a better way to do this (maybe if user specifies `#[avro(with)]` also use that for the default)
331        // Disable getting the field default, if the schema is not retrieved from the field type
332        if with != With::Trait && avro.default == FieldDefault::Trait {
333            avro.default = FieldDefault::Disabled;
334        }
335
336        if ((serde.skip_serializing && !serde.skip_deserializing)
337            || serde.skip_serializing_if.is_some())
338            && avro.default == FieldDefault::Disabled
339        {
340            errors.push(syn::Error::new(
341                span,
342                "`#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = false)]`"
343            ));
344        }
345
346        if !errors.is_empty() {
347            return Err(errors);
348        }
349
350        let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
351
352        Ok(Self {
353            doc,
354            default: avro.default,
355            alias: serde.alias,
356            rename: serde.rename,
357            skip: serde.skip || (serde.skip_serializing && serde.skip_deserializing),
358            flatten: serde.flatten,
359            with,
360        })
361    }
362}
363
364fn extract_rustdoc(attributes: &[Attribute]) -> Option<String> {
365    let doc = attributes
366        .iter()
367        .filter(|attr| attr.style == AttrStyle::Outer && attr.path().is_ident("doc"))
368        .filter_map(|attr| {
369            let name_value = attr.meta.require_name_value();
370            match name_value {
371                Ok(name_value) => match &name_value.value {
372                    syn::Expr::Lit(expr_lit) => match expr_lit.lit {
373                        syn::Lit::Str(ref lit_str) => Some(lit_str.value().trim().to_string()),
374                        _ => None,
375                    },
376                    _ => None,
377                },
378                Err(_) => None,
379            }
380        })
381        .collect::<Vec<String>>()
382        .join("\n");
383    if doc.is_empty() { None } else { Some(doc) }
384}
385
386fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
387    let msg = format!("{e}");
388    let token_errors = e.write_errors();
389    vec![syn::Error::new(token_errors.span(), msg)]
390}
391
392#[cfg(nightly)]
393/// Emit a compiler warning.
394///
395/// This is a no-op when the `nightly` feature is not enabled.
396fn warn(span: Span, message: &str, help: &str) {
397    proc_macro::Diagnostic::spanned(span.unwrap(), proc_macro::Level::Warning, message)
398        .help(help)
399        .emit();
400}
401
402#[cfg(not(nightly))]
403/// Emit a compiler warning.
404///
405/// This is a no-op when the `nightly` feature is not enabled.
406fn warn(_span: Span, _message: &str, _help: &str) {}