klickhouse_derive/
attr.rs

1use crate::ctxt::Ctxt;
2use crate::respan::respan;
3use crate::symbol::*;
4use proc_macro2::{Span, TokenStream, TokenTree};
5use quote::ToTokens;
6use syn::parse::{self, Parse, ParseStream};
7use syn::punctuated::Punctuated;
8use syn::{Expr, Ident, Meta};
9
10// This module handles parsing of `#[klickhouse(...)]` attributes. The entrypoints
11// are `attr::Container::from_ast`, `attr::Variant::from_ast`, and
12// `attr::Field::from_ast`. Each returns an instance of the corresponding
13// struct. Note that none of them return a Result. Unrecognized, malformed, or
14// duplicated attributes result in a span_err but otherwise are ignored. The
15// user will see errors simultaneously for all bad attributes in the crate
16// rather than just the first.
17
18pub use crate::case::RenameRule;
19
20struct Attr<'c, T> {
21    cx: &'c Ctxt,
22    name: Symbol,
23    tokens: TokenStream,
24    value: Option<T>,
25}
26
27impl<'c, T> Attr<'c, T> {
28    fn none(cx: &'c Ctxt, name: Symbol) -> Self {
29        Attr {
30            cx,
31            name,
32            tokens: TokenStream::new(),
33            value: None,
34        }
35    }
36
37    fn set<A: ToTokens>(&mut self, obj: A, value: T) {
38        let tokens = obj.into_token_stream();
39
40        if self.value.is_some() {
41            self.cx.error_spanned_by(
42                tokens,
43                format!("duplicate klickhouse attribute `{}`", self.name),
44            );
45        } else {
46            self.tokens = tokens;
47            self.value = Some(value);
48        }
49    }
50
51    fn set_opt<A: ToTokens>(&mut self, obj: A, value: Option<T>) {
52        if let Some(value) = value {
53            self.set(obj, value);
54        }
55    }
56
57    fn set_if_none(&mut self, value: T) {
58        if self.value.is_none() {
59            self.value = Some(value);
60        }
61    }
62
63    fn get(self) -> Option<T> {
64        self.value
65    }
66}
67
68struct BoolAttr<'c>(Attr<'c, ()>);
69
70impl<'c> BoolAttr<'c> {
71    fn none(cx: &'c Ctxt, name: Symbol) -> Self {
72        BoolAttr(Attr::none(cx, name))
73    }
74
75    fn set_true<A: ToTokens>(&mut self, obj: A) {
76        self.0.set(obj, ());
77    }
78
79    fn get(&self) -> bool {
80        self.0.value.is_some()
81    }
82}
83
84pub struct Name {
85    name: String,
86    renamed: bool,
87}
88
89#[allow(deprecated)]
90fn unraw(ident: &Ident) -> String {
91    // str::trim_start_matches was added in 1.30, trim_left_matches deprecated
92    // in 1.33. We currently support rustc back to 1.15 so we need to continue
93    // to use the deprecated one.
94    ident.to_string().trim_left_matches("r#").to_owned()
95}
96
97impl Name {
98    fn from_attrs(source_name: String, rename: Attr<String>) -> Name {
99        let rename = rename.get();
100        Name {
101            renamed: rename.is_some(),
102            name: rename.unwrap_or_else(|| source_name.clone()),
103        }
104    }
105
106    /// Return the container name for the container when serializing.
107    pub fn name(&self) -> String {
108        self.name.clone()
109    }
110}
111
112/// Represents struct or enum attribute information.
113pub struct Container {
114    deny_unknown_fields: bool,
115    default: Default,
116    rename_all_rule: RenameRule,
117    bound: Option<Vec<syn::WherePredicate>>,
118    type_from: Option<syn::Type>,
119    type_try_from: Option<syn::Type>,
120    type_into: Option<syn::Type>,
121    is_packed: bool,
122}
123
124impl Container {
125    /// Extract out the `#[klickhouse(...)]` attributes from an item.
126    pub fn from_ast(cx: &Ctxt, item: &syn::DeriveInput) -> Self {
127        let mut rename = Attr::none(cx, RENAME);
128        let mut deny_unknown_fields = BoolAttr::none(cx, DENY_UNKNOWN_FIELDS);
129        let mut default = Attr::none(cx, DEFAULT);
130        let mut rename_all_rule = Attr::none(cx, RENAME_ALL);
131        let mut bound = Attr::none(cx, BOUND);
132        let mut type_from = Attr::none(cx, FROM);
133        let mut type_try_from = Attr::none(cx, TRY_FROM);
134        let mut type_into = Attr::none(cx, INTO);
135
136        for meta_item in item
137            .attrs
138            .iter()
139            .flat_map(|attr| get_klickhouse_meta_items(cx, attr))
140            .flatten()
141        {
142            match &meta_item {
143                // Parse `#[klickhouse(rename = "foo")]`
144                Meta::NameValue(m) if m.path == RENAME => {
145                    let Expr::Lit(expr_lit) = &m.value else {
146                        continue;
147                    };
148
149                    if let Ok(s) = get_lit_str(cx, RENAME, &expr_lit.lit) {
150                        rename.set(&m.path, s.value());
151                    }
152                }
153
154                // Parse `#[klickhouse(rename_all = "foo")]`
155                Meta::NameValue(m) if m.path == RENAME_ALL => {
156                    let Expr::Lit(expr_lit) = &m.value else {
157                        continue;
158                    };
159
160                    if let Ok(s) = get_lit_str(cx, RENAME_ALL, &expr_lit.lit) {
161                        match RenameRule::from_str(&s.value()) {
162                            Ok(rename_rule) => {
163                                rename_all_rule.set(&m.path, rename_rule);
164                            }
165                            Err(err) => cx.error_spanned_by(s, err),
166                        }
167                    }
168                }
169
170                // Parse `#[klickhouse(deny_unknown_fields)]`
171                Meta::Path(word) if word == DENY_UNKNOWN_FIELDS => {
172                    deny_unknown_fields.set_true(word);
173                }
174
175                // Parse `#[klickhouse(default)]`
176                Meta::Path(word) if word == DEFAULT => match &item.data {
177                    syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields {
178                        syn::Fields::Named(_) => {
179                            default.set(word, Default::Default);
180                        }
181                        syn::Fields::Unnamed(_) | syn::Fields::Unit => cx.error_spanned_by(
182                            fields,
183                            "#[klickhouse(default)] can only be used on structs with named fields",
184                        ),
185                    },
186                    syn::Data::Enum(syn::DataEnum { enum_token, .. }) => cx.error_spanned_by(
187                        enum_token,
188                        "#[klickhouse(default)] can only be used on structs with named fields",
189                    ),
190                    syn::Data::Union(syn::DataUnion { union_token, .. }) => cx.error_spanned_by(
191                        union_token,
192                        "#[klickhouse(default)] can only be used on structs with named fields",
193                    ),
194                },
195
196                // Parse `#[klickhouse(default = "...")]`
197                Meta::NameValue(m) if m.path == DEFAULT => {
198                    let Expr::Lit(expr_lit) = &m.value else {
199                        continue;
200                    };
201
202                    if let Ok(path) = parse_lit_into_expr_path(cx, DEFAULT, &expr_lit.lit) {
203                        match &item.data {
204                            syn::Data::Struct(syn::DataStruct { fields, .. }) => {
205                                match fields {
206                                    syn::Fields::Named(_) => {
207                                        default.set(&m.path, Default::Path(path));
208                                    }
209                                    syn::Fields::Unnamed(_) | syn::Fields::Unit => cx
210                                        .error_spanned_by(
211                                            fields,
212                                            "#[klickhouse(default = \"...\")] can only be used on structs with named fields",
213                                        ),
214                                }
215                            }
216                            syn::Data::Enum(syn::DataEnum { enum_token, .. }) => cx
217                                .error_spanned_by(
218                                    enum_token,
219                                    "#[klickhouse(default = \"...\")] can only be used on structs with named fields",
220                                ),
221                            syn::Data::Union(syn::DataUnion {
222                                union_token, ..
223                            }) => cx.error_spanned_by(
224                                union_token,
225                                "#[klickhouse(default = \"...\")] can only be used on structs with named fields",
226                            ),
227                        }
228                    }
229                }
230
231                // Parse `#[klickhouse(bound = "T: SomeBound")]`
232                Meta::NameValue(m) if m.path == BOUND => {
233                    let Expr::Lit(expr_lit) = &m.value else {
234                        continue;
235                    };
236
237                    if let Ok(where_predicates) =
238                        parse_lit_into_where(cx, BOUND, BOUND, &expr_lit.lit)
239                    {
240                        bound.set(&m.path, where_predicates.clone());
241                    }
242                }
243
244                // Parse `#[klickhouse(from = "Type")]
245                Meta::NameValue(m) if m.path == FROM => {
246                    let Expr::Lit(expr_lit) = &m.value else {
247                        continue;
248                    };
249
250                    if let Ok(from_ty) = parse_lit_into_ty(cx, FROM, &expr_lit.lit) {
251                        type_from.set_opt(&m.path, Some(from_ty));
252                    }
253                }
254
255                // Parse `#[klickhouse(try_from = "Type")]
256                Meta::NameValue(m) if m.path == TRY_FROM => {
257                    let Expr::Lit(expr_lit) = &m.value else {
258                        continue;
259                    };
260
261                    if let Ok(try_from_ty) = parse_lit_into_ty(cx, TRY_FROM, &expr_lit.lit) {
262                        type_try_from.set_opt(&m.path, Some(try_from_ty));
263                    }
264                }
265
266                // Parse `#[klickhouse(into = "Type")]
267                Meta::NameValue(m) if m.path == INTO => {
268                    let Expr::Lit(expr_lit) = &m.value else {
269                        continue;
270                    };
271
272                    if let Ok(into_ty) = parse_lit_into_ty(cx, INTO, &expr_lit.lit) {
273                        type_into.set_opt(&m.path, Some(into_ty));
274                    }
275                }
276
277                meta_item => {
278                    let path = meta_item
279                        .path()
280                        .into_token_stream()
281                        .to_string()
282                        .replace(' ', "");
283                    cx.error_spanned_by(
284                        meta_item.path(),
285                        format!("unknown klickhouse container attribute `{}`", path),
286                    );
287                }
288            }
289        }
290
291        let mut is_packed = false;
292        for attr in &item.attrs {
293            if attr.path().is_ident("repr") {
294                let _ = attr.parse_args_with(|input: ParseStream| {
295                    while let Some(token) = input.parse()? {
296                        if let TokenTree::Ident(ident) = token {
297                            is_packed |= ident == "packed";
298                        }
299                    }
300                    Ok(())
301                });
302            }
303        }
304
305        Container {
306            deny_unknown_fields: deny_unknown_fields.get(),
307            default: default.get().unwrap_or(Default::None),
308            rename_all_rule: rename_all_rule.get().unwrap_or(RenameRule::None),
309            bound: bound.get(),
310            type_from: type_from.get(),
311            type_try_from: type_try_from.get(),
312            type_into: type_into.get(),
313            is_packed,
314        }
315    }
316
317    pub fn rename_all_rule(&self) -> &RenameRule {
318        &self.rename_all_rule
319    }
320
321    pub fn deny_unknown_fields(&self) -> bool {
322        self.deny_unknown_fields
323    }
324
325    pub fn default(&self) -> &Default {
326        &self.default
327    }
328
329    pub fn bound(&self) -> Option<&[syn::WherePredicate]> {
330        self.bound.as_ref().map(|vec| &vec[..])
331    }
332
333    pub fn type_from(&self) -> Option<&syn::Type> {
334        self.type_from.as_ref()
335    }
336
337    pub fn type_try_from(&self) -> Option<&syn::Type> {
338        self.type_try_from.as_ref()
339    }
340
341    pub fn type_into(&self) -> Option<&syn::Type> {
342        self.type_into.as_ref()
343    }
344
345    pub fn is_packed(&self) -> bool {
346        self.is_packed
347    }
348}
349
350/// Represents field attribute information
351pub struct Field {
352    name: Name,
353    skip_serializing: bool,
354    skip_deserializing: bool,
355    default: Default,
356    serialize_with: Option<syn::ExprPath>,
357    deserialize_with: Option<syn::ExprPath>,
358    bound: Option<Vec<syn::WherePredicate>>,
359    nested: bool,
360    flatten: bool,
361}
362
363#[allow(clippy::enum_variant_names)]
364/// Represents the default to use for a field when deserializing.
365pub enum Default {
366    /// Field must always be specified because it does not have a default.
367    None,
368    /// The default is given by `std::default::Default::default()`.
369    Default,
370    /// The default is given by this function.
371    Path(syn::ExprPath),
372}
373
374impl Field {
375    /// Extract out the `#[klickhouse(...)]` attributes from a struct field.
376    pub fn from_ast(
377        cx: &Ctxt,
378        index: usize,
379        field: &syn::Field,
380        container_default: &Default,
381    ) -> Self {
382        let mut rename = Attr::none(cx, RENAME);
383        let mut nested = BoolAttr::none(cx, NESTED);
384        let mut skip_serializing = BoolAttr::none(cx, SKIP_SERIALIZING);
385        let mut skip_deserializing = BoolAttr::none(cx, SKIP_DESERIALIZING);
386        let mut flatten = BoolAttr::none(cx, FLATTEN);
387        let mut default = Attr::none(cx, DEFAULT);
388        let mut serialize_with = Attr::none(cx, SERIALIZE_WITH);
389        let mut deserialize_with = Attr::none(cx, DESERIALIZE_WITH);
390        let mut bound = Attr::none(cx, BOUND);
391
392        let ident = match &field.ident {
393            Some(ident) => unraw(ident),
394            None => index.to_string(),
395        };
396
397        for meta_item in field
398            .attrs
399            .iter()
400            .flat_map(|attr| get_klickhouse_meta_items(cx, attr))
401            .flatten()
402        {
403            match &meta_item {
404                // Parse `#[klickhouse(rename = "foo")]`
405                Meta::NameValue(m) if m.path == RENAME => {
406                    let Expr::Lit(expr_lit) = &m.value else {
407                        continue;
408                    };
409
410                    if let Ok(s) = get_lit_str(cx, RENAME, &expr_lit.lit) {
411                        rename.set(&m.path, s.value());
412                    }
413                }
414
415                // Parse `#[klickhouse(default)]`
416                Meta::Path(word) if word == DEFAULT => {
417                    default.set(word, Default::Default);
418                }
419
420                // Parse `#[klickhouse(default = "...")]`
421                Meta::NameValue(m) if m.path == DEFAULT => {
422                    let Expr::Lit(expr_lit) = &m.value else {
423                        continue;
424                    };
425
426                    if let Ok(path) = parse_lit_into_expr_path(cx, DEFAULT, &expr_lit.lit) {
427                        default.set(&m.path, Default::Path(path));
428                    }
429                }
430
431                // Parse `#[klickhouse(skip_serializing)]`
432                Meta::Path(word) if word == SKIP_SERIALIZING => {
433                    skip_serializing.set_true(word);
434                }
435
436                // Parse `#[klickhouse(nested)]`
437                Meta::Path(word) if word == NESTED => {
438                    nested.set_true(word);
439                }
440
441                // Parse `#[klickhouse(flatten)]`
442                Meta::Path(word) if word == FLATTEN => {
443                    flatten.set_true(word);
444                }
445
446                // Parse `#[klickhouse(skip_deserializing)]`
447                Meta::Path(word) if word == SKIP_DESERIALIZING => {
448                    skip_deserializing.set_true(word);
449                }
450
451                // Parse `#[klickhouse(skip)]`
452                Meta::Path(word) if word == SKIP => {
453                    skip_serializing.set_true(word);
454                    skip_deserializing.set_true(word);
455                }
456
457                // Parse `#[klickhouse(serialize_with = "...")]`
458                Meta::NameValue(m) if m.path == SERIALIZE_WITH => {
459                    let Expr::Lit(expr_lit) = &m.value else {
460                        continue;
461                    };
462
463                    if let Ok(path) = parse_lit_into_expr_path(cx, SERIALIZE_WITH, &expr_lit.lit) {
464                        serialize_with.set(&m.path, path);
465                    }
466                }
467
468                // Parse `#[klickhouse(deserialize_with = "...")]`
469                Meta::NameValue(m) if m.path == DESERIALIZE_WITH => {
470                    let Expr::Lit(expr_lit) = &m.value else {
471                        continue;
472                    };
473
474                    if let Ok(path) = parse_lit_into_expr_path(cx, DESERIALIZE_WITH, &expr_lit.lit)
475                    {
476                        deserialize_with.set(&m.path, path);
477                    }
478                }
479
480                // Parse `#[klickhouse(with = "...")]`
481                Meta::NameValue(m) if m.path == WITH => {
482                    let Expr::Lit(expr_lit) = &m.value else {
483                        continue;
484                    };
485
486                    if let Ok(path) = parse_lit_into_expr_path(cx, WITH, &expr_lit.lit) {
487                        let mut ser_path = path.clone();
488                        ser_path
489                            .path
490                            .segments
491                            .push(Ident::new("to_sql", Span::call_site()).into());
492                        serialize_with.set(&m.path, ser_path);
493                        let mut de_path = path;
494                        de_path
495                            .path
496                            .segments
497                            .push(Ident::new("from_sql", Span::call_site()).into());
498                        deserialize_with.set(&m.path, de_path);
499                    }
500                }
501
502                // Parse `#[klickhouse(bound = "T: SomeBound")]`
503                Meta::NameValue(m) if m.path == BOUND => {
504                    let Expr::Lit(expr_lit) = &m.value else {
505                        continue;
506                    };
507
508                    if let Ok(where_predicates) =
509                        parse_lit_into_where(cx, BOUND, BOUND, &expr_lit.lit)
510                    {
511                        bound.set(&m.path, where_predicates.clone());
512                    }
513                }
514
515                meta_item => {
516                    let path = meta_item
517                        .path()
518                        .into_token_stream()
519                        .to_string()
520                        .replace(' ', "");
521                    cx.error_spanned_by(
522                        meta_item.path(),
523                        format!("unknown klickhouse field attribute `{}`", path),
524                    );
525                }
526            }
527        }
528
529        // Is skip_deserializing, initialize the field to Default::default() unless a
530        // different default is specified by `#[klickhouse(default = "...")]` on
531        // ourselves or our container (e.g. the struct we are in).
532        if let Default::None = *container_default {
533            if skip_deserializing.0.value.is_some() {
534                default.set_if_none(Default::Default);
535            }
536        }
537
538        Field {
539            name: Name::from_attrs(ident, rename),
540            skip_serializing: skip_serializing.get(),
541            skip_deserializing: skip_deserializing.get(),
542            default: default.get().unwrap_or(Default::None),
543            serialize_with: serialize_with.get(),
544            deserialize_with: deserialize_with.get(),
545            bound: bound.get(),
546            nested: nested.get(),
547            flatten: flatten.get(),
548        }
549    }
550
551    pub fn name(&self) -> &Name {
552        &self.name
553    }
554
555    pub fn rename_by_rules(&mut self, rules: &RenameRule) {
556        if !self.name.renamed {
557            self.name.name = rules.apply_to_field(&self.name.name);
558        }
559    }
560
561    pub fn flatten(&self) -> bool {
562        self.flatten
563    }
564
565    pub fn nested(&self) -> bool {
566        self.nested
567    }
568
569    pub fn skip_serializing(&self) -> bool {
570        self.skip_serializing
571    }
572
573    pub fn skip_deserializing(&self) -> bool {
574        self.skip_deserializing
575    }
576
577    pub fn default(&self) -> &Default {
578        &self.default
579    }
580
581    pub fn serialize_with(&self) -> Option<&syn::ExprPath> {
582        self.serialize_with.as_ref()
583    }
584
585    pub fn deserialize_with(&self) -> Option<&syn::ExprPath> {
586        self.deserialize_with.as_ref()
587    }
588
589    pub fn bound(&self) -> Option<&[syn::WherePredicate]> {
590        self.bound.as_ref().map(|vec| &vec[..])
591    }
592}
593
594pub fn get_klickhouse_meta_items(cx: &Ctxt, attr: &syn::Attribute) -> Result<Vec<syn::Meta>, ()> {
595    if !attr.path().is_ident(&KLICKHOUSE) {
596        return Ok(Vec::new());
597    }
598
599    let nested = match attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
600        Ok(nested) => nested,
601        Err(err) => {
602            cx.syn_error(err);
603
604            return Err(());
605        }
606    };
607
608    Ok(nested.into_iter().collect())
609}
610
611fn get_lit_str<'a>(cx: &Ctxt, attr_name: Symbol, lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> {
612    get_lit_str2(cx, attr_name, attr_name, lit)
613}
614
615fn get_lit_str2<'a>(
616    cx: &Ctxt,
617    attr_name: Symbol,
618    meta_item_name: Symbol,
619    lit: &'a syn::Lit,
620) -> Result<&'a syn::LitStr, ()> {
621    if let syn::Lit::Str(lit) = lit {
622        Ok(lit)
623    } else {
624        cx.error_spanned_by(
625            lit,
626            format!(
627                "expected klickhouse {} attribute to be a string: `{} = \"...\"`",
628                attr_name, meta_item_name
629            ),
630        );
631        Err(())
632    }
633}
634
635fn parse_lit_into_expr_path(
636    cx: &Ctxt,
637    attr_name: Symbol,
638    lit: &syn::Lit,
639) -> Result<syn::ExprPath, ()> {
640    let string = get_lit_str(cx, attr_name, lit)?;
641    parse_lit_str(string).map_err(|_| {
642        cx.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value()))
643    })
644}
645
646fn parse_lit_into_where(
647    cx: &Ctxt,
648    attr_name: Symbol,
649    meta_item_name: Symbol,
650    lit: &syn::Lit,
651) -> Result<Vec<syn::WherePredicate>, ()> {
652    let string = get_lit_str2(cx, attr_name, meta_item_name, lit)?;
653    if string.value().is_empty() {
654        return Ok(Vec::new());
655    }
656
657    let where_string = syn::LitStr::new(&format!("where {}", string.value()), string.span());
658
659    parse_lit_str::<syn::WhereClause>(&where_string)
660        .map(|wh| wh.predicates.into_iter().collect())
661        .map_err(|err| cx.error_spanned_by(lit, err))
662}
663
664fn parse_lit_into_ty(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result<syn::Type, ()> {
665    let string = get_lit_str(cx, attr_name, lit)?;
666
667    parse_lit_str(string).map_err(|_| {
668        cx.error_spanned_by(
669            lit,
670            format!("failed to parse type: {} = {:?}", attr_name, string.value()),
671        )
672    })
673}
674
675fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T>
676where
677    T: Parse,
678{
679    let tokens = spanned_tokens(s)?;
680    syn::parse2(tokens)
681}
682
683fn spanned_tokens(s: &syn::LitStr) -> parse::Result<TokenStream> {
684    let stream = syn::parse_str(&s.value())?;
685    Ok(respan(stream, s.span()))
686}