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;
21use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned};
22
23mod avro;
24mod serde;
25
26#[derive(Default)]
27pub struct NamedTypeOptions {
28    pub name: String,
29    pub doc: Option<String>,
30    pub aliases: Vec<String>,
31    pub rename_all: RenameRule,
32    pub transparent: bool,
33}
34
35impl NamedTypeOptions {
36    pub fn new(
37        ident: &Ident,
38        attributes: &[Attribute],
39        span: Span,
40    ) -> Result<Self, Vec<syn::Error>> {
41        let avro =
42            avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
43        let serde =
44            serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
45
46        // Check for deprecated attributes
47        avro.deprecated(span);
48
49        // Collect errors so user gets all feedback at once
50        let mut errors = Vec::new();
51
52        // Check for any Serde attributes that are hard errors
53        if serde.tag.is_some()
54            || serde.content.is_some()
55            || serde.untagged
56            || serde.variant_identifier
57            || serde.field_identifier
58        {
59            errors.push(syn::Error::new(
60                span,
61                "AvroSchema derive does not support changing the tagging Serde generates (`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)",
62            ));
63        }
64        if serde.remote.is_some() {
65            errors.push(syn::Error::new(
66                span,
67                "AvroSchema derive does not support the Serde `remote` attribute",
68            ));
69        }
70        if serde.rename_all.deserialize != serde.rename_all.serialize {
71            errors.push(syn::Error::new(
72                span,
73                "AvroSchema derive does not support different rename rules for serializing and deserializing (`rename_all(serialize = \"..\", deserialize = \"..\")`)"
74            ));
75        }
76
77        // Check for conflicts between Serde and Avro
78        if avro.name.is_some() && avro.name != serde.rename {
79            errors.push(syn::Error::new(
80                span,
81                "#[avro(name = \"..\")] must match #[serde(rename = \"..\")], it's also deprecated. Please use only `#[serde(rename = \"..\")]`",
82            ));
83        }
84        if avro.rename_all != RenameRule::None && serde.rename_all.serialize != avro.rename_all {
85            errors.push(syn::Error::new(
86                span,
87                "#[avro(rename_all = \"..\")] must match #[serde(rename_all = \"..\")], it's also deprecated. Please use only `#[serde(rename_all = \"..\")]`",
88            ));
89        }
90        if serde.transparent
91            && (serde.rename.is_some()
92                || avro.name.is_some()
93                || avro.namespace.is_some()
94                || avro.doc.is_some()
95                || !avro.alias.is_empty()
96                || avro.rename_all != RenameRule::None
97                || serde.rename_all.serialize != RenameRule::None
98                || serde.rename_all.deserialize != RenameRule::None)
99        {
100            errors.push(syn::Error::new(
101                span,
102                "AvroSchema: #[serde(transparent)] is incompatible with all other attributes",
103            ));
104        }
105
106        if !errors.is_empty() {
107            return Err(errors);
108        }
109
110        let name = serde.rename.unwrap_or(ident.to_string());
111        let full_schema_name = vec![avro.namespace, Some(name)]
112            .into_iter()
113            .flatten()
114            .collect::<Vec<String>>()
115            .join(".");
116
117        let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
118
119        Ok(Self {
120            name: full_schema_name,
121            doc,
122            aliases: avro.alias,
123            rename_all: serde.rename_all.serialize,
124            transparent: serde.transparent,
125        })
126    }
127}
128
129pub struct VariantOptions {
130    pub rename: Option<String>,
131}
132
133impl VariantOptions {
134    pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, Vec<syn::Error>> {
135        let avro = avro::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
136        let serde =
137            serde::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
138
139        // Check for deprecated attributes
140        avro.deprecated(span);
141
142        // Collect errors so user gets all feedback at once
143        let mut errors = Vec::new();
144
145        // Check for any Serde attributes that are hard errors
146        if serde.other || serde.untagged {
147            errors.push(syn::Error::new(
148                span,
149                "AvroSchema derive does not support changing the tagging Serde generates (`other`, `untagged`)",
150            ));
151        }
152
153        // Check for conflicts between Serde and Avro
154        if avro.rename.is_some() && serde.rename != avro.rename {
155            errors.push(syn::Error::new(
156                span,
157                "`#[avro(rename = \"..\")]` must match `#[serde(rename = \"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
158            ));
159        }
160
161        if !errors.is_empty() {
162            return Err(errors);
163        }
164
165        Ok(Self {
166            rename: serde.rename,
167        })
168    }
169}
170
171/// How to get the schema for this field or variant.
172#[derive(Debug, PartialEq, Default)]
173pub enum With {
174    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
175    #[default]
176    Trait,
177    /// Use `module::get_schema_in_ctxt` where the module is defined by Serde's `with` attribute.
178    Serde(Path),
179    /// Call the function in this expression.
180    Expr(Expr),
181}
182
183impl With {
184    fn from_avro_and_serde(
185        avro: &avro::With,
186        serde: &Option<String>,
187        span: Span,
188    ) -> Result<Self, syn::Error> {
189        match &avro {
190            avro::With::Trait => Ok(Self::Trait),
191            avro::With::Serde => {
192                if let Some(serde) = serde {
193                    let path = Path::from_string(serde).map_err(|err| {
194                        syn::Error::new(
195                            span,
196                            format!(
197                                "AvroSchema: Expected a path for `#[serde(with = \"..\")]`: {err:?}"
198                            ),
199                        )
200                    })?;
201                    Ok(Self::Serde(path))
202                } else {
203                    Err(syn::Error::new(
204                        span,
205                        "`#[avro(with)]` requires `#[serde(with = \"some_module\")]` or provide a function to call `#[avro(with = some_fn)]`",
206                    ))
207                }
208            }
209            avro::With::Expr(expr) => Ok(Self::Expr(expr.clone())),
210        }
211    }
212}
213
214#[derive(Default)]
215pub struct FieldOptions {
216    pub doc: Option<String>,
217    pub default: Option<String>,
218    pub alias: Vec<String>,
219    pub rename: Option<String>,
220    pub skip: bool,
221    pub flatten: bool,
222    pub with: With,
223}
224
225impl FieldOptions {
226    pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, Vec<syn::Error>> {
227        let mut avro =
228            avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
229        let mut serde =
230            serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
231        // Sort the aliases, so our check for equality does not fail if they are provided in a different order
232        avro.alias.sort();
233        serde.alias.sort();
234
235        // Check for deprecated attributes
236        avro.deprecated(span);
237
238        // Collect errors so user gets all feedback at once
239        let mut errors = Vec::new();
240
241        // Check for any Serde attributes that are hard errors
242        if serde.getter.is_some() {
243            errors.push(syn::Error::new(
244                span,
245                "AvroSchema derive does not support the Serde `getter` attribute",
246            ));
247        }
248
249        // Check for conflicts between Serde and Avro
250        if avro.skip && !(serde.skip || (serde.skip_serializing && serde.skip_deserializing)) {
251            errors.push(syn::Error::new(
252                span,
253                "`#[avro(skip)]` requires `#[serde(skip)]`, it's also deprecated. Please use only `#[serde(skip)]`"
254            ));
255        }
256        if avro.flatten && !serde.flatten {
257            errors.push(syn::Error::new(
258                span,
259                "`#[avro(flatten)]` requires `#[serde(flatten)]`, it's also deprecated. Please use only `#[serde(flatten)]`"
260            ));
261        }
262        // TODO: rename and alias checking can be relaxed with a more complex check, would require the field name
263        if avro.rename.is_some() && serde.rename != avro.rename {
264            errors.push(syn::Error::new(
265                span,
266                "`#[avro(rename = \"..\")]` must match `#[serde(rename = \"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
267            ));
268        }
269        if !avro.alias.is_empty() && serde.alias != avro.alias {
270            errors.push(syn::Error::new(
271                span,
272                "`#[avro(alias = \"..\")]` must match `#[serde(alias = \"..\")]`, it's also deprecated. Please use only `#[serde(alias = \"..\")]`"
273            ));
274        }
275        if ((serde.skip_serializing && !serde.skip_deserializing)
276            || serde.skip_serializing_if.is_some())
277            && avro.default.is_none()
278        {
279            errors.push(syn::Error::new(
280                span,
281                "`#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` require `#[avro(default = \"..\")]`"
282            ));
283        }
284        let with = match With::from_avro_and_serde(&avro.with, &serde.with, span) {
285            Ok(with) => with,
286            Err(error) => {
287                errors.push(error);
288                // This won't actually be used, but it does simplify the code
289                With::Trait
290            }
291        };
292
293        if !errors.is_empty() {
294            return Err(errors);
295        }
296
297        let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
298
299        Ok(Self {
300            doc,
301            default: avro.default,
302            alias: serde.alias,
303            rename: serde.rename,
304            skip: serde.skip || (serde.skip_serializing && serde.skip_deserializing),
305            flatten: serde.flatten,
306            with,
307        })
308    }
309}
310
311fn extract_rustdoc(attributes: &[Attribute]) -> Option<String> {
312    let doc = attributes
313        .iter()
314        .filter(|attr| attr.style == AttrStyle::Outer && attr.path().is_ident("doc"))
315        .filter_map(|attr| {
316            let name_value = attr.meta.require_name_value();
317            match name_value {
318                Ok(name_value) => match &name_value.value {
319                    syn::Expr::Lit(expr_lit) => match expr_lit.lit {
320                        syn::Lit::Str(ref lit_str) => Some(lit_str.value().trim().to_string()),
321                        _ => None,
322                    },
323                    _ => None,
324                },
325                Err(_) => None,
326            }
327        })
328        .collect::<Vec<String>>()
329        .join("\n");
330    if doc.is_empty() { None } else { Some(doc) }
331}
332
333fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
334    let msg = format!("{e}");
335    let token_errors = e.write_errors();
336    vec![syn::Error::new(token_errors.span(), msg)]
337}
338
339#[cfg(nightly)]
340/// Emit a compiler warning.
341///
342/// This is a no-op when the `nightly` feature is not enabled.
343fn warn(span: Span, message: &str, help: &str) {
344    proc_macro::Diagnostic::spanned(span.unwrap(), proc_macro::Level::Warning, message)
345        .help(help)
346        .emit()
347}
348
349#[cfg(not(nightly))]
350/// Emit a compiler warning.
351///
352/// This is a no-op when the `nightly` feature is not enabled.
353fn warn(_span: Span, _message: &str, _help: &str) {}