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
10pub 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 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 pub fn name(&self) -> String {
108 self.name.clone()
109 }
110}
111
112pub 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 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 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 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 Meta::Path(word) if word == DENY_UNKNOWN_FIELDS => {
172 deny_unknown_fields.set_true(word);
173 }
174
175 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 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 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 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 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 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
350pub 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)]
364pub enum Default {
366 None,
368 Default,
370 Path(syn::ExprPath),
372}
373
374impl Field {
375 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 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 Meta::Path(word) if word == DEFAULT => {
417 default.set(word, Default::Default);
418 }
419
420 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 Meta::Path(word) if word == SKIP_SERIALIZING => {
433 skip_serializing.set_true(word);
434 }
435
436 Meta::Path(word) if word == NESTED => {
438 nested.set_true(word);
439 }
440
441 Meta::Path(word) if word == FLATTEN => {
443 flatten.set_true(word);
444 }
445
446 Meta::Path(word) if word == SKIP_DESERIALIZING => {
448 skip_deserializing.set_true(word);
449 }
450
451 Meta::Path(word) if word == SKIP => {
453 skip_serializing.set_true(word);
454 skip_deserializing.set_true(word);
455 }
456
457 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 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 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 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 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}