1#![deny(missing_docs)]
5#![doc = include_str!("../README.md")]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7
8use std::collections::HashSet;
9
10use darling::{FromAttributes as _, util::SpannedValue};
11use proc_macro2::{Literal, Span, TokenStream};
12use quote::{ToTokens, quote, quote_spanned};
13use syn::{Attribute, Path, spanned::Spanned};
14use synstructure::{BindingInfo, Structure, VariantInfo};
15
16macro_rules! decl_derive {
17 ($name:ident, $derive_fn:ident) => {
18synstructure::decl_derive!([$name, attributes(entry)] =>
19 $derive_fn
133);
134 }
135}
136
137decl_derive!(Entry, derive_entry);
138decl_derive!(MetriqueEntry, derive_metrique_entry);
139
140fn derive_entry(input: Structure<'_>) -> TokenStream {
141 tokens_or_compiler_err(try_derive(input, "e!(::metrique_writer)))
142}
143
144fn derive_metrique_entry(input: Structure<'_>) -> TokenStream {
145 tokens_or_compiler_err(try_derive(input, "e!(::metrique::writer)))
146}
147
148#[derive(darling::FromAttributes)]
150#[darling(attributes(entry))]
151struct ParsedFieldMetricAttr {
152 name: Option<SpannedValue<String>>,
153 sample_group: Option<SpannedValue<()>>,
154 ignore: Option<SpannedValue<()>>,
155 flatten: Option<SpannedValue<()>>,
156 timestamp: Option<SpannedValue<()>>,
157 format: Option<SpannedValue<Path>>,
158}
159
160enum FieldMetricAttr {
162 Ignore,
163 Flatten,
164 Timestamp(Span),
165 NamedValue {
166 name: Option<SpannedValue<String>>,
167 format: Option<SpannedValue<Path>>,
168 sample_group: Option<Span>,
169 },
170}
171
172impl FieldMetricAttr {
173 fn try_parse(field_span: Span, attrs: &[Attribute]) -> syn::Result<Self> {
174 match ParsedFieldMetricAttr::from_attributes(attrs)? {
175 ParsedFieldMetricAttr {
176 name,
177 sample_group,
178 format,
179 ignore: None,
180 flatten: None,
181 timestamp: None,
182 } => {
183 if let Some(name) = name.as_ref()
184 && name.is_empty()
185 {
186 return Err(syn::Error::new(name.span(), "`name` can't be empty"));
187 }
188 Ok(Self::NamedValue {
189 name,
190 sample_group: sample_group.map(|g| g.span()),
191 format,
192 })
193 }
194
195 ParsedFieldMetricAttr {
196 name: None,
197 sample_group: None,
198 ignore: Some(_ignore),
199 flatten: None,
200 timestamp: None,
201 format: None,
202 } => Ok(Self::Ignore),
203
204 ParsedFieldMetricAttr {
205 name: None,
206 sample_group: None,
207 ignore: None,
208 flatten: Some(_flatten),
209 timestamp: None,
210 format: None,
211 } => Ok(Self::Flatten),
212
213 ParsedFieldMetricAttr {
214 name: None,
215 sample_group: None,
216 ignore: None,
217 flatten: None,
218 timestamp: Some(timestamp),
219 format: None,
220 } => Ok(Self::Timestamp(timestamp.span())),
221
222 _ => Err(syn::Error::new(
223 field_span,
224 "can only combine `name` and `sample_group` in `#[entry]`",
225 )),
226 }
227 }
228}
229
230#[derive(darling::FromAttributes)]
232#[darling(attributes(entry))]
233struct ContainerMetricAttr {
234 rename_all: Option<SpannedValue<String>>,
235}
236
237impl ContainerMetricAttr {
238 fn merge_with_defaults_from(self, root: &Self) -> Self {
239 Self {
240 rename_all: self.rename_all.or_else(|| root.rename_all.clone()),
241 }
242 }
243}
244
245fn try_derive(input: Structure<'_>, krate: &TokenStream) -> syn::Result<TokenStream> {
246 let span = input.ast().span();
247
248 let container_attr = match &input.ast().data {
249 syn::Data::Struct(_) | syn::Data::Enum(_) => {
250 ContainerMetricAttr::from_attributes(&input.ast().attrs)?
251 }
252 syn::Data::Union(_) => {
253 return Err(syn::Error::new(span, "can't derive `Entry` for unions"));
254 }
255 };
256
257 let mut writes = Vec::new();
258 let mut sample_groups = Vec::new();
259 let has_multiple_variants = input.variants().len() > 1;
260 for variant in input.variants() {
261 let EntryVariant {
262 write,
263 sample_group,
264 } = derive_variant(variant, &container_attr, has_multiple_variants, krate)?;
265 writes.push(write);
266 sample_groups.push(sample_group);
267 }
268
269 Ok(input.gen_impl(quote_spanned! {span=>
270 gen impl #krate::core::entry::Entry for @Self {
271 fn write<'a>(&'a self, writer: &mut impl #krate::core::entry::EntryWriter<'a>) {
272 match *self {
273 #(#writes)*
274 }
275 }
276
277 fn sample_group(&self) -> impl ::std::iter::Iterator<Item = (::std::borrow::Cow<'static, str>, ::std::borrow::Cow<'static, str>)> {
278 match *self {
279 #(#sample_groups)*
280 }
281 }
282 }
283 }))
284}
285
286fn tokens_or_compiler_err(result: syn::Result<TokenStream>) -> TokenStream {
287 match result {
288 Ok(t) => t,
289 Err(e) => e.into_compile_error(),
290 }
291}
292
293fn make_binary_tree_chain(iterators: Vec<TokenStream>) -> TokenStream {
300 fn chain_once(stack: &mut Vec<(TokenStream, usize)>, allow_different_degree: bool) -> bool {
301 if stack.len() < 2 {
302 return false; }
304 if !allow_different_degree && (stack[stack.len() - 2].1 != stack[stack.len() - 1].1) {
305 return false; }
307 let (rhs, rhs_deg) = stack.pop().unwrap();
308 let (lhs, lhs_deg) = stack.pop().unwrap();
309 stack.push((
310 quote!(#lhs.chain(#rhs)),
311 std::cmp::max(lhs_deg, rhs_deg) + 1,
312 ));
313 true
314 }
315
316 let mut stack = vec![];
317 for elem in iterators {
318 stack.push((elem, 0));
319 while chain_once(&mut stack, false) {}
320 }
321 while chain_once(&mut stack, true) {}
322 if let Some((elem, _deg)) = stack.pop() {
323 elem
324 } else {
325 quote!(::std::iter::empty())
326 }
327}
328
329fn derive_variant(
330 variant: &VariantInfo,
331 root_container_attr: &ContainerMetricAttr,
332 has_multiple_variants: bool,
333 krate: &TokenStream,
334) -> syn::Result<EntryVariant> {
335 let container_attr = ContainerMetricAttr::from_attributes(variant.ast().attrs)?
336 .merge_with_defaults_from(root_container_attr);
337
338 let mut fields = FieldSet {
339 namer: Namer {
340 rename_all: container_attr
341 .rename_all
342 .map_or(Ok(None), |r| NameStyle::try_parse(r.span(), &r).map(Some))?,
343 ..Default::default()
344 },
345 ..Default::default()
346 };
347
348 let mut errors: Vec<syn::Error> = variant
349 .bindings()
350 .iter()
351 .flat_map(|field| fields.add(field, krate).err())
352 .collect();
353 if let Some(mut error) = errors.pop() {
354 error.extend(errors);
356 Err(error)
357 } else {
358 let pat = variant.pat();
359 let FieldSet {
360 writes,
361 sample_groups,
362 ..
363 } = fields;
364
365 let write = quote!(#pat => { #(#writes)* });
366 let sample_group_iter = make_binary_tree_chain(sample_groups);
367 let sample_group = if has_multiple_variants {
368 quote!(#pat => Box::new(#sample_group_iter) as Box<dyn ::std::iter::Iterator<Item = _>>,)
372 } else {
373 quote!(#pat => #sample_group_iter,)
374 };
375 Ok(EntryVariant {
376 write,
377 sample_group,
378 })
379 }
380}
381
382struct EntryVariant {
383 write: TokenStream,
384 sample_group: TokenStream,
385}
386
387#[derive(Default)]
388struct FieldSet {
389 namer: Namer,
390 has_timestamp: bool,
391 writes: Vec<TokenStream>,
392 sample_groups: Vec<TokenStream>,
393}
394
395impl FieldSet {
396 fn add(&mut self, field: &BindingInfo<'_>, krate: &TokenStream) -> syn::Result<()> {
397 match FieldMetricAttr::try_parse(field.span(), &field.ast().attrs)? {
398 FieldMetricAttr::NamedValue {
399 name,
400 sample_group,
401 format,
402 } => {
403 let name = Literal::string(&if let Some(name) = name {
404 self.namer.specified(&name)?
405 } else {
406 self.namer.unspecified(field)?
407 });
408
409 let field_tokens: TokenStream = match format {
410 None => field.to_token_stream(),
411 Some(format) => {
412 let format = &*format;
413 quote_spanned! {field.binding.span() =>
414 &#krate::core::value::FormattedValue::<_, #format, _>::new(#field)
415 }
416 }
417 };
418 self.writes.push(quote_spanned! {field.binding.span()=>
419 #krate::core::entry::EntryWriter::value(writer, #name, #field_tokens);
420 });
421 if sample_group.is_some() {
422 self.sample_groups
423 .push(quote_spanned! {field.binding.span()=>
424 ::std::iter::once((
425 ::std::borrow::Cow::Borrowed(#name),
426 #[allow(clippy::useless_conversion)]
427 {
428 #krate::core::SampleGroup::as_sample_group(#field)
429 },
430 ))
431 });
432 }
433 }
434 FieldMetricAttr::Ignore => {}
435 FieldMetricAttr::Flatten => {
436 self.writes.push(quote_spanned! {field.binding.span()=>
437 #krate::core::entry::Entry::write(#field, writer);
438 });
439 self.sample_groups
440 .push(quote_spanned! {field.binding.span()=>
441 #krate::core::entry::Entry::sample_group(#field)
442 });
443 }
444 FieldMetricAttr::Timestamp(span) => {
445 if self.has_timestamp {
446 return Err(syn::Error::new(
447 span,
448 "can't have more than one `timestamp`",
449 ));
450 } else {
451 self.has_timestamp = true;
452 self.writes.push(quote_spanned! {field.binding.span()=>
455 #[allow(clippy::useless_conversion)]
456 {
457 #krate::core::entry::EntryWriter::timestamp(writer, (*#field).into());
458 }
459 });
460 }
461 }
462 };
463 Ok(())
464 }
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Default)]
469struct Namer {
470 names: HashSet<String>,
471 rename_all: Option<NameStyle>,
472}
473
474impl Namer {
475 fn specified(&mut self, name: &SpannedValue<String>) -> syn::Result<String> {
476 self.try_add(name.span(), name)
477 }
478
479 fn unspecified(&mut self, field: &BindingInfo<'_>) -> syn::Result<String> {
480 let Some(ident) = field.ast().ident.as_ref() else {
481 return Err(syn::Error::new(
482 field.span(),
483 "must specify `name` for tuple fields",
484 ));
485 };
486 let name = ident.to_string();
487 let name = self.rename_all.map(|r| r.apply(&name)).unwrap_or(name);
488 self.try_add(ident.span(), &name)
489 }
490
491 fn try_add(&mut self, span: Span, name: &str) -> syn::Result<String> {
492 if self.names.insert(name.into()) {
493 Ok(name.into())
494 } else {
495 Err(syn::Error::new(
496 span,
497 format!("name `{name}` is used more than once"),
498 ))
499 }
500 }
501}
502
503#[allow(clippy::enum_variant_names)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
505enum NameStyle {
506 LowerCase,
507 UpperCase,
508 PascalCase,
509 CamelCase,
510 SnakeCase,
511 ScreamingSnakeCase,
512 KebabCase,
513 ScreamingKebabCase,
514}
515
516impl NameStyle {
517 fn try_parse(span: Span, style: &str) -> syn::Result<Self> {
518 match style {
519 "lowercase" => Ok(Self::LowerCase),
520 "UPPERCASE" => Ok(Self::UpperCase),
521 "PascalCase" => Ok(Self::PascalCase),
522 "camelCase" => Ok(Self::CamelCase),
523 "snake_case" => Ok(Self::SnakeCase),
524 "SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase),
525 "kebab-case" => Ok(Self::KebabCase),
526 "SCREAMING-KEBAB-CASE" => Ok(Self::ScreamingKebabCase),
527 _ => Err(syn::Error::new(
528 span,
529 format!("unknown name style `{style}`"),
530 )),
531 }
532 }
533
534 fn apply(self, name: &str) -> String {
535 use inflector::Inflector;
536 match self {
537 NameStyle::LowerCase => name.to_ascii_lowercase(),
538 NameStyle::UpperCase => name.to_ascii_uppercase(),
539 NameStyle::PascalCase => name.to_pascal_case(),
540 NameStyle::CamelCase => name.to_camel_case(),
541 NameStyle::SnakeCase => name.to_snake_case(),
542 NameStyle::ScreamingSnakeCase => name.to_screaming_snake_case(),
543 NameStyle::KebabCase => name.to_kebab_case(),
544 NameStyle::ScreamingKebabCase => name.to_kebab_case().to_ascii_uppercase(),
545 }
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn test_binary_tree_chain() {
555 assert_eq!(
556 make_binary_tree_chain(vec![]).to_string(),
557 quote! {::std::iter::empty()}.to_string()
558 );
559 assert_eq!(
560 make_binary_tree_chain(vec![quote! {1}]).to_string(),
561 quote! {1}.to_string()
562 );
563 assert_eq!(
564 make_binary_tree_chain(vec![quote! {1}, quote! {2}]).to_string(),
565 quote! {1 .chain(2)}.to_string()
566 );
567 assert_eq!(make_binary_tree_chain(vec![quote!{1},quote!{2},quote!{3},quote!{4},quote!{5},quote!{6},quote!{7},quote!{8},quote!{9}]).to_string(),
568 quote!{1 . chain (2) . chain (3 . chain (4)) . chain (5 . chain (6) . chain (7 . chain (8))) . chain (9)}.to_string());
569 assert_eq!(make_binary_tree_chain(vec![quote!{1},quote!{2},quote!{3},quote!{4},quote!{5},quote!{6},quote!{7},quote!{8},quote!{9},quote!{10},quote!{11}]).to_string(),
570 quote!{1 . chain (2) . chain (3 . chain (4)) . chain (5 . chain (6) . chain (7 . chain (8))) . chain (9 . chain (10) . chain (11))}.to_string());
571 }
572
573 #[test]
574 fn derives_struct_entry() {
575 synstructure::test_derive! {
576 derive_entry {
577 #[entry(rename_all = "PascalCase")]
578 struct TestEntry {
579 #[entry(timestamp)]
580 start: SystemTime,
581 foo: String,
582 bar: String,
583 #[entry(sample_group)]
584 operation: &'static str,
585 #[entry(name = "GREAT_COUNTER")]
586 some_counter: u64,
587 #[entry(ignore)]
588 ignored: bool,
589 #[entry(flatten)]
590 sub_entry: SubEntry,
591 #[entry(format = my::Formatter)]
592 custom_format: bool,
593 }
594 }
595 expands to {
596 const _: () = {
597 impl ::metrique_writer::core::entry::Entry for TestEntry {
598 fn write<'a>(&'a self, writer: &mut impl ::metrique_writer::core::entry::EntryWriter<'a>) {
599 match *self {
600 TestEntry {
601 start: ref __binding_0,
602 foo: ref __binding_1,
603 bar: ref __binding_2,
604 operation: ref __binding_3,
605 some_counter: ref __binding_4,
606 ignored: ref __binding_5,
607 sub_entry: ref __binding_6,
608 custom_format: ref __binding_7,
609 } => {
610 #[allow(clippy::useless_conversion)]
611 {
612 ::metrique_writer::core::entry::EntryWriter::timestamp(writer, (*__binding_0).into());
613 }
614 ::metrique_writer::core::entry::EntryWriter::value(writer, "Foo", __binding_1);
615 ::metrique_writer::core::entry::EntryWriter::value(writer, "Bar", __binding_2);
616 ::metrique_writer::core::entry::EntryWriter::value(writer, "Operation", __binding_3);
617 ::metrique_writer::core::entry::EntryWriter::value(writer, "GREAT_COUNTER", __binding_4);
618 ::metrique_writer::core::entry::Entry::write(__binding_6, writer);
619 ::metrique_writer::core::entry::EntryWriter::value(writer, "CustomFormat",
620 &::metrique_writer::core::value::FormattedValue::<_, my::Formatter, _>::new(__binding_7));
621 }
622 }
623 }
624
625 fn sample_group(&self) -> impl ::std::iter::Iterator<Item = (::std::borrow::Cow<'static, str>, ::std::borrow::Cow<'static, str>)> {
626 match *self {
627 TestEntry {
628 start: ref __binding_0,
629 foo: ref __binding_1,
630 bar: ref __binding_2,
631 operation: ref __binding_3,
632 some_counter: ref __binding_4,
633 ignored: ref __binding_5,
634 sub_entry: ref __binding_6,
635 custom_format: ref __binding_7,
636 } =>
637 ::std::iter::once((
638 ::std::borrow::Cow::Borrowed("Operation"),
639 #[allow(clippy::useless_conversion)]
640 {
641 ::metrique_writer::core::SampleGroup::as_sample_group(__binding_3)
642 },
643 ))
644 .chain(::metrique_writer::core::entry::Entry::sample_group(__binding_6)),
645 }
646 }
647 }
648 };
649 }
650 no_build
651 }
652 }
653
654 #[test]
655 fn derives_struct_entry_metrique() {
656 synstructure::test_derive! {
657 derive_metrique_entry {
658 #[entry(rename_all = "PascalCase")]
659 struct TestEntry {
660 #[entry(timestamp)]
661 start: SystemTime,
662 }
663 }
664 expands to {
665 const _: () = {
666 impl ::metrique::writer::core::entry::Entry for TestEntry {
667 fn write<'a>(&'a self, writer: &mut impl ::metrique::writer::core::entry::EntryWriter<'a>) {
668 match *self {
669 TestEntry {
670 start: ref __binding_0,
671 } => {
672 #[allow(clippy::useless_conversion)]
673 {
674 ::metrique::writer::core::entry::EntryWriter::timestamp(writer, (*__binding_0).into());
675 }
676 }
677 }
678 }
679
680 fn sample_group(&self) -> impl ::std::iter::Iterator<Item = (::std::borrow::Cow<'static, str>, ::std::borrow::Cow<'static, str>)> {
681 match *self {
682 TestEntry {
683 start: ref __binding_0,
684 } => ::std::iter::empty(),
685 }
686 }
687 }
688 };
689 }
690 no_build
691 }
692 }
693
694 #[test]
695 fn checks_duplicate_names() {
696 synstructure::test_derive! {
697 derive_entry {
698 struct TestEntry {
699 first: String,
700 #[entry(name = "first")]
701 second: String
702 }
703 }
704 expands to {
705 ::core::compile_error! { "name `first` is used more than once" }
706 }
707 no_build
708 }
709 }
710
711 #[test]
712 fn checks_duplicate_timestamps() {
713 synstructure::test_derive! {
714 derive_entry {
715 struct TestEntry {
716 #[entry(timestamp)]
717 first: String,
718 #[entry(timestamp)]
719 second: String
720 }
721 }
722 expands to {
723 ::core::compile_error! { "can't have more than one `timestamp`" }
724 }
725 no_build
726 }
727 }
728
729 #[test]
730 fn derives_enum_entry() {
731 synstructure::test_derive! {
732 derive_entry {
733 #[entry(rename_all = "PascalCase")]
734 enum TestEntry {
735 First(#[entry(flatten)] FirstEntry),
736 Second {
737 #[entry(sample_group)]
738 test: &'static str,
739 #[entry(timestamp)]
740 time: SystemTime,
741 some_counter: u64,
742 },
743 Third(#[entry(name = "CanNameTuples")] u64)
744 }
745 }
746 expands to {
747 const _: () = {
748 impl ::metrique_writer::core::entry::Entry for TestEntry {
749 fn write<'a>(&'a self, writer: &mut impl ::metrique_writer::core::entry::EntryWriter<'a>) {
750 match *self {
751 TestEntry::First(ref __binding_0,) => {
752 ::metrique_writer::core::entry::Entry::write(__binding_0, writer);
753 }
754 TestEntry::Second { test: ref __binding_0, time: ref __binding_1, some_counter: ref __binding_2, } => {
755 ::metrique_writer::core::entry::EntryWriter::value(writer, "Test", __binding_0);
756 #[allow(clippy::useless_conversion)]
757 {
758 ::metrique_writer::core::entry::EntryWriter::timestamp(writer, (*__binding_1).into());
759 }
760 ::metrique_writer::core::entry::EntryWriter::value(writer, "SomeCounter", __binding_2);
761 }
762 TestEntry::Third(ref __binding_0,) => {
763 ::metrique_writer::core::entry::EntryWriter::value(writer, "CanNameTuples", __binding_0);
764 }
765 }
766 }
767
768 fn sample_group(&self) -> impl ::std::iter::Iterator<Item = (::std::borrow::Cow<'static, str>, ::std::borrow::Cow<'static, str>)> {
769 match *self {
770 TestEntry::First(ref __binding_0,) =>
771 Box::new(
772 ::metrique_writer::core::entry::Entry::sample_group(__binding_0)
773 ) as Box<dyn ::std::iter::Iterator<Item = _>>,
774 TestEntry::Second { test: ref __binding_0, time: ref __binding_1, some_counter: ref __binding_2, } =>
775 Box::new(
776 ::std::iter::once((
777 ::std::borrow::Cow::Borrowed("Test"),
778 #[allow(clippy::useless_conversion)]
779 {
780 ::metrique_writer::core::SampleGroup::as_sample_group(__binding_0)
781 },
782 ))
783 ) as Box<dyn ::std::iter::Iterator<Item = _>>,
784 TestEntry::Third(ref __binding_0,) =>
785 Box::new(
786 ::std::iter::empty()
787 ) as Box<dyn ::std::iter::Iterator<Item = _>>,
788 }
789 }
790 }
791 };
792 }
793 no_build
794 }
795 }
796}