vector_config_macros/ast/
mod.rs

1use darling::{ast::NestedMeta, error::Accumulator, util::path_to_string, FromMeta};
2use quote::ToTokens;
3use serde_derive_internals::{ast as serde_ast, attr as serde_attr};
4
5mod container;
6mod field;
7mod util;
8mod variant;
9
10pub use container::Container;
11pub use field::Field;
12use syn::Expr;
13pub use variant::Variant;
14use vector_config_common::constants;
15
16const INVALID_VALUE_EXPR: &str =
17    "got function call-style literal value but could not parse as expression";
18
19/// The style of a data container, applying to both enum variants and structs.
20///
21/// This mirrors the type by the same name in `serde_derive_internal`.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Style {
24    /// Named fields.
25    Struct,
26
27    /// Multiple unnamed fields.
28    Tuple,
29
30    /// Single unnamed field.
31    Newtype,
32
33    /// No fields.
34    Unit,
35}
36
37impl From<serde_ast::Style> for Style {
38    fn from(style: serde_ast::Style) -> Self {
39        match style {
40            serde_ast::Style::Struct => Style::Struct,
41            serde_ast::Style::Tuple => Style::Tuple,
42            serde_ast::Style::Newtype => Style::Newtype,
43            serde_ast::Style::Unit => Style::Unit,
44        }
45    }
46}
47
48/// The tagging configuration for an enum.
49///
50/// This mirrors the type by the nearly-same name (`TagType`) in `serde_derive_internal`.
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub enum Tagging {
53    /// The default.
54    ///
55    /// ```json
56    /// {"variant1": {"key1": "value1", "key2": "value2"}}
57    /// ```
58    External,
59
60    /// `#[serde(tag = "type")]`
61    ///
62    /// ```json
63    /// {"type": "variant1", "key1": "value1", "key2": "value2"}
64    /// ```
65    Internal { tag: String },
66
67    /// `#[serde(tag = "t", content = "c")]`
68    ///
69    /// ```json
70    /// {"t": "variant1", "c": {"key1": "value1", "key2": "value2"}}
71    /// ```
72    Adjacent { tag: String, content: String },
73
74    /// `#[serde(untagged)]`
75    ///
76    /// ```json
77    /// {"key1": "value1", "key2": "value2"}
78    /// ```
79    None,
80}
81
82impl Tagging {
83    /// Generates custom attributes that describe the tagging mode.
84    ///
85    /// This is typically added to the metadata for an enum's overall schema to better describe how
86    /// the various subschemas relate to each other and how they're used on the Rust side, for the
87    /// purpose of generating usable documentation from the schema.
88    pub fn as_enum_metadata(&self) -> Vec<LazyCustomAttribute> {
89        match self {
90            Self::External => vec![LazyCustomAttribute::kv(
91                constants::DOCS_META_ENUM_TAGGING,
92                "external",
93            )],
94            Self::Internal { tag } => vec![
95                LazyCustomAttribute::kv(constants::DOCS_META_ENUM_TAGGING, "internal"),
96                LazyCustomAttribute::kv(constants::DOCS_META_ENUM_TAG_FIELD, tag),
97            ],
98            Self::Adjacent { tag, content } => vec![
99                LazyCustomAttribute::kv(constants::DOCS_META_ENUM_TAGGING, "adjacent"),
100                LazyCustomAttribute::kv(constants::DOCS_META_ENUM_TAG_FIELD, tag),
101                LazyCustomAttribute::kv(constants::DOCS_META_ENUM_CONTENT_FIELD, content),
102            ],
103            Self::None => vec![LazyCustomAttribute::kv(
104                constants::DOCS_META_ENUM_TAGGING,
105                "untagged",
106            )],
107        }
108    }
109}
110
111impl From<&serde_attr::TagType> for Tagging {
112    fn from(tag: &serde_attr::TagType) -> Self {
113        match tag {
114            serde_attr::TagType::External => Tagging::External,
115            serde_attr::TagType::Internal { tag } => Tagging::Internal { tag: tag.clone() },
116            serde_attr::TagType::Adjacent { tag, content } => Tagging::Adjacent {
117                tag: tag.clone(),
118                content: content.clone(),
119            },
120            serde_attr::TagType::None => Tagging::None,
121        }
122    }
123}
124
125/// The fields of the data container.
126///
127/// This mirrors the type by the same name in `serde_derive_internal`.
128pub enum Data<'a> {
129    Enum(Vec<Variant<'a>>),
130    Struct(Style, Vec<Field<'a>>),
131}
132
133/// A lazy version of `CustomAttribute`.
134///
135/// This is used to capture the value at the macro callsite without having to evaluate it, which
136/// lets us generate code where, for example, the value of a metadata key/value pair can be
137/// evaluated by an expression given in the attribute.
138///
139/// This is similar to how `serde` takes an expression for things like `#[serde(default =
140/// "exprhere")]`, and so on.
141#[derive(Clone, Debug)]
142pub enum LazyCustomAttribute {
143    /// A standalone flag.
144    Flag(String),
145
146    /// A key/value pair.
147    KeyValue {
148        key: String,
149        value: proc_macro2::TokenStream,
150    },
151}
152
153impl LazyCustomAttribute {
154    pub fn kv<K, V>(key: K, value: V) -> Self
155    where
156        K: std::fmt::Display,
157        V: ToTokens,
158    {
159        Self::KeyValue {
160            key: key.to_string(),
161            value: value.to_token_stream(),
162        }
163    }
164}
165
166/// Metadata items defined on containers, variants, or fields.
167#[derive(Clone, Debug)]
168pub struct Metadata {
169    items: Vec<LazyCustomAttribute>,
170}
171
172impl Metadata {
173    pub fn attributes(&self) -> impl Iterator<Item = LazyCustomAttribute> {
174        self.items.clone().into_iter()
175    }
176}
177
178impl FromMeta for Metadata {
179    fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
180        let mut errors = Accumulator::default();
181
182        // Can't be empty.
183        if items.is_empty() {
184            errors.push(darling::Error::too_few_items(1));
185        }
186
187        errors = errors.checkpoint()?;
188
189        // Can't either be name/value pairs or single items i.e. flags.
190        let meta_items = items
191            .iter()
192            .filter_map(|nmeta| match nmeta {
193                NestedMeta::Meta(meta) => match meta {
194                    syn::Meta::Path(path) => Some(LazyCustomAttribute::Flag(path_to_string(path))),
195                    syn::Meta::List(_) => {
196                        errors.push(darling::Error::unexpected_type("list").with_span(nmeta));
197                        None
198                    }
199                    syn::Meta::NameValue(nv) => match &nv.value {
200                        Expr::Lit(expr) => {
201                            match &expr.lit {
202                                // When dealing with a string literal, we check if it ends in `()`. If so,
203                                // we emit that as-is, leading to doing a function call and using the return
204                                // value of that function as the value for this key/value pair.
205                                //
206                                // Otherwise, we just treat the string literal normally.
207                                syn::Lit::Str(s) => {
208                                    if s.value().ends_with("()") {
209                                        if let Ok(expr) = s.parse::<Expr>() {
210                                            Some(LazyCustomAttribute::KeyValue {
211                                                key: path_to_string(&nv.path),
212                                                value: expr.to_token_stream(),
213                                            })
214                                        } else {
215                                            errors.push(
216                                                darling::Error::custom(INVALID_VALUE_EXPR)
217                                                    .with_span(nmeta),
218                                            );
219                                            None
220                                        }
221                                    } else {
222                                        Some(LazyCustomAttribute::KeyValue {
223                                            key: path_to_string(&nv.path),
224                                            value: s.value().to_token_stream(),
225                                        })
226                                    }
227                                }
228                                lit => Some(LazyCustomAttribute::KeyValue {
229                                    key: path_to_string(&nv.path),
230                                    value: lit.to_token_stream(),
231                                }),
232                            }
233                        }
234                        expr => {
235                            errors
236                                .push(darling::Error::unexpected_expr_type(expr).with_span(nmeta));
237                            None
238                        }
239                    },
240                },
241                NestedMeta::Lit(_) => {
242                    errors.push(darling::Error::unexpected_type("literal").with_span(nmeta));
243                    None
244                }
245            })
246            .collect::<Vec<_>>();
247
248        errors.finish_with(Metadata { items: meta_items })
249    }
250}