1#![deny(missing_docs)]
5#![doc = include_str!("../README.md")]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7
8mod aggregate;
9mod derive_utils;
10mod emf;
11mod entry_impl;
12mod enums;
13mod inflect;
14mod structs;
15mod value_impl;
16
17use darling::{
18 FromField, FromMeta,
19 ast::NestedMeta,
20 util::{Flag, SpannedValue},
21};
22use emf::DimensionSets;
23use inflect::NameStyle;
24use proc_macro::TokenStream;
25use proc_macro2::{Span, TokenStream as Ts2};
26use quote::{ToTokens, quote, quote_spanned};
27use syn::{
28 Attribute, Data, DeriveInput, Error, Fields, GenericParam, Generics, Ident, Result, Type,
29 Visibility, parse_macro_input, spanned::Spanned,
30};
31
32use crate::inflect::{name_contains_dot, name_contains_uninflectables, name_ends_with_delimiter};
33
34#[proc_macro_attribute]
381pub fn metrics(attr: TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
382 let input = parse_macro_input!(input as DeriveInput);
383
384 let mut base_token_stream = Ts2::new();
389 let root_attrs = match parse_root_attrs(attr) {
390 Ok(root_attrs) => root_attrs,
391 Err(e) => {
392 e.to_compile_error().to_tokens(&mut base_token_stream);
394 RootAttributes::default()
395 }
396 };
397
398 match generate_metrics(root_attrs, input.clone()) {
400 Ok(output) => output.to_tokens(&mut base_token_stream),
401 Err(err) => {
402 clean_base_adt(&input).to_tokens(&mut base_token_stream);
404 err.to_compile_error().to_tokens(&mut base_token_stream);
406 }
407 };
408 base_token_stream.into()
409}
410
411#[proc_macro_attribute]
550pub fn aggregate(attr: TokenStream, input: TokenStream) -> TokenStream {
551 let input = parse_macro_input!(input as DeriveInput);
552 let attr_str = attr.to_string();
553 let entry_mode = attr.is_empty() || attr_str.trim() != "direct";
554 let enable_merge_ref = attr_str.contains("ref");
555
556 let mut output = Ts2::new();
557
558 let struct_result = aggregate::generate_aggregated_struct(&input, entry_mode);
560 let impl_result = aggregate::generate_aggregate_strategy_impl(&input, entry_mode);
561 let merge_ref_result = aggregate::generate_merge_ref_impl(&input, entry_mode, enable_merge_ref);
562 let merge_methods_result = aggregate::generate_merge_on_drop_methods(&input, entry_mode);
563
564 match (
565 struct_result,
566 impl_result,
567 merge_ref_result,
568 merge_methods_result,
569 ) {
570 (Ok(aggregated_struct), Ok(aggregate_impl), Ok(merge_ref_impl), Ok(merge_methods)) => {
571 aggregated_struct.to_tokens(&mut output);
572 aggregate_impl.to_tokens(&mut output);
573 if let Some(merge_ref) = merge_ref_impl {
574 merge_ref.to_tokens(&mut output);
575 }
576 merge_methods.to_tokens(&mut output);
577 aggregate::clean_aggregate_adt(&input).to_tokens(&mut output);
578 }
579 (Err(e), _, _, _) | (_, Err(e), _, _) | (_, _, Err(e), _) | (_, _, _, Err(e)) => {
580 aggregate::clean_aggregate_adt(&input).to_tokens(&mut output);
582 e.to_compile_error().to_tokens(&mut output);
583 }
584 }
585
586 output.into()
587}
588
589#[derive(Copy, Clone, Debug)]
590enum OwnershipKind {
591 ByRef,
592 ByValue,
593}
594
595#[derive(Debug, Default, FromMeta)]
596#[darling(from_word = Self::from_word)]
598struct ValueAttributes {
599 string: Flag,
600}
601
602impl ValueAttributes {
603 fn from_word() -> darling::Result<Self> {
605 Ok(Self::default())
606 }
607}
608
609#[derive(Debug, Clone)]
611pub(crate) enum Tag {
612 Inflectable { name: String, sample_group: bool },
613 Exact { name: String, sample_group: bool },
614}
615
616impl Tag {
617 pub(crate) fn field_name(&self, root_attrs: &RootAttributes) -> String {
619 match self {
620 Tag::Inflectable { name, .. } => root_attrs
621 .prefix
622 .as_ref()
623 .map(|p| p.apply(name, root_attrs.rename_all))
624 .unwrap_or_else(|| root_attrs.rename_all.apply(name)),
625 Tag::Exact { name, .. } => name.clone(),
626 }
627 }
628
629 pub(crate) fn sample_group(&self) -> bool {
630 match self {
631 Tag::Inflectable { sample_group, .. } => *sample_group,
632 Tag::Exact { sample_group, .. } => *sample_group,
633 }
634 }
635}
636
637#[derive(Debug, FromMeta)]
638#[darling(and_then = Self::validate, from_word = Self::from_word)]
639struct RawTag {
640 #[darling(default)]
641 name: Option<SpannedKv<String>>,
642 #[darling(default)]
643 name_exact: Option<SpannedKv<String>>,
644 #[darling(default)]
645 sample_group: Flag,
646}
647
648impl RawTag {
649 fn from_word() -> darling::Result<Self> {
650 Err(darling::Error::custom(
651 "tag requires either name or name_exact parameter: #[metrics(tag(name = \"...\"))] or #[metrics(tag(name_exact = \"...\"))]",
652 ))
653 }
654
655 fn validate(self) -> darling::Result<Self> {
656 match (self.name, self.name_exact) {
657 (None, None) => Err(darling::Error::custom(
658 "tag requires either name or name_exact parameter: #[metrics(tag(name = \"...\"))] or #[metrics(tag(name_exact = \"...\"))]",
659 )),
660 (Some(_), Some(_)) => Err(darling::Error::custom(
661 "tag cannot have both name and name_exact parameters",
662 )),
663 (Some(name), None) => Ok(Self {
664 name: Some(validate_name(name)?),
665 name_exact: None,
666 sample_group: self.sample_group,
667 }),
668 (None, Some(name_exact)) => Ok(Self {
669 name: None,
670 name_exact: Some(validate_name(name_exact)?),
671 sample_group: self.sample_group,
672 }),
673 }
674 }
675}
676
677impl From<RawTag> for Tag {
678 fn from(raw: RawTag) -> Self {
679 let sample_group = raw.sample_group.is_present();
680 match (raw.name, raw.name_exact) {
681 (Some(name), None) => Tag::Inflectable {
682 name: name.value,
683 sample_group,
684 },
685 (None, Some(name)) => Tag::Exact {
686 name: name.value,
687 sample_group,
688 },
689 _ => unreachable!("validated in RawTag::validate"),
690 }
691 }
692}
693
694#[derive(Debug, Clone, Default)]
701pub(crate) struct FlagsList(pub(crate) Vec<syn::Path>);
702
703impl FromMeta for FlagsList {
704 fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
705 match item {
706 syn::Meta::List(list) => {
707 let parsed: syn::punctuated::Punctuated<syn::Path, syn::Token![,]> = list
708 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
709 .map_err(|e| darling::Error::custom(e.to_string()).with_span(list))?;
710 Ok(FlagsList(parsed.into_iter().collect()))
711 }
712 _ => Err(darling::Error::custom("expected flags(Path, ...)").with_span(item)),
713 }
714 }
715}
716
717#[derive(Debug, Default, FromMeta)]
718struct RawRootAttributes {
719 prefix: Option<SpannedKv<String>>,
720 exact_prefix: Option<SpannedKv<String>>,
721
722 #[darling(default)]
723 rename_all: NameStyle,
724
725 #[darling(rename = "emf::dimension_sets")]
726 emf_dimensions: Option<DimensionSets>,
727
728 tag: Option<SpannedValue<RawTag>>,
729
730 subfield: Flag,
731 #[darling(rename = "subfield_owned")]
732 subfield_owned: Flag,
733 #[darling(rename = "sample_group")]
734 sample_group: Flag,
735 value: Option<ValueAttributes>,
736}
737
738#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
739enum MetricMode {
740 #[default]
741 RootEntry,
742 Subfield,
743 SubfieldOwned,
744 Value,
745 ValueString,
746}
747
748#[derive(Debug, Default)]
749struct RootAttributes {
750 prefix: Option<Prefix>,
751
752 rename_all: NameStyle,
753
754 emf_dimensions: Option<DimensionSets>,
755
756 tag: Option<Tag>,
757
758 sample_group: bool,
759
760 mode: MetricMode,
761}
762
763impl RawRootAttributes {
764 fn validate(self) -> darling::Result<RootAttributes> {
765 let mut out: Option<(MetricMode, &'static str)> = None;
766 if let Some(value_attrs) = self.value {
767 if value_attrs.string.is_present() {
768 out = set_exclusive(
769 |_| MetricMode::ValueString,
770 "value",
771 out,
772 &value_attrs.string,
773 )?
774 } else {
775 out = Some((MetricMode::Value, "value"));
776 }
777 }
778 out = set_exclusive(|_| MetricMode::Subfield, "subfield", out, &self.subfield)?;
779 out = set_exclusive(
780 |_| MetricMode::SubfieldOwned,
781 "subfield_owned",
782 out,
783 &self.subfield_owned,
784 )?;
785 let mut mode = out.map(|(s, _)| s).unwrap_or_default();
786 let sample_group = if self.sample_group.is_present() {
787 if let MetricMode::Value = &mut mode {
788 true
789 } else {
790 return Err(darling::Error::custom(
791 "`sample_group` as a top-level attribute can only be used with #[metrics(value)]",
792 )
793 .with_span(&self.sample_group.span()));
794 }
795 } else {
796 false
797 };
798 if let (MetricMode::ValueString, Some(ds)) = (mode, &self.emf_dimensions) {
799 return Err(
800 darling::Error::custom("value does not make sense with dimension-sets")
801 .with_span(&ds.span()),
802 );
803 }
804 let tag = self
805 .tag
806 .map(|tag| match &mode {
807 MetricMode::RootEntry | MetricMode::Subfield | MetricMode::SubfieldOwned => {
808 Ok(tag.into_inner().into())
809 }
810 MetricMode::Value | MetricMode::ValueString => Err(darling::Error::custom(
811 "value and value(string) do not support tag",
812 )
813 .with_span(&tag.span())),
814 })
815 .transpose()?;
816
817 Ok(RootAttributes {
818 prefix: Prefix::from_inflectable_and_exact(
819 &self.prefix,
820 &self.exact_prefix,
821 PrefixLevel::Root,
822 )?
823 .map(SpannedValue::into_inner),
824 rename_all: self.rename_all,
825 emf_dimensions: self.emf_dimensions,
826 tag,
827 sample_group,
828 mode,
829 })
830 }
831}
832
833impl RootAttributes {
834 fn configuration_field_names(&self) -> Vec<Ts2> {
835 if let Some(_dims) = &self.emf_dimensions {
836 vec![quote! { __config__ }]
837 } else {
838 vec![]
839 }
840 }
841
842 fn configuration_fields(&self) -> Vec<Ts2> {
843 let mut fields = vec![];
844 if let Some(_dims) = &self.emf_dimensions {
845 fields.push(quote! {
846 __config__: ::metrique::emf::SetEntryDimensions
847 })
848 }
849 fields
850 }
851
852 fn create_configuration(&self) -> Vec<Ts2> {
853 let mut fields = vec![];
854 if let Some(dims) = &self.emf_dimensions {
855 fields
856 .push(quote! { __config__: ::metrique::__plumbing_entry_dimensions!(dims: #dims) })
857 }
858 fields
859 }
860
861 fn ownership_kind(&self) -> OwnershipKind {
862 match self.mode {
863 MetricMode::RootEntry | MetricMode::SubfieldOwned => OwnershipKind::ByValue,
864 MetricMode::Subfield | MetricMode::ValueString | MetricMode::Value => {
865 OwnershipKind::ByRef
866 }
867 }
868 }
869
870 fn warnings(&self) -> Ts2 {
871 quote! {}
872 }
873}
874
875#[derive(Debug, FromField)]
876#[darling(attributes(metrics))]
877struct RawMetricsFieldAttrs {
878 flatten: Flag,
879
880 flatten_entry: Flag,
881
882 no_close: Flag,
883
884 timestamp: Flag,
885
886 sample_group: Flag,
887
888 ignore: Flag,
889
890 #[darling(default)]
891 unit: Option<SpannedKv<syn::Path>>,
892
893 #[darling(default)]
894 format: Option<SpannedKv<syn::Path>>,
895
896 #[darling(default)]
897 name: Option<SpannedKv<String>>,
898
899 #[darling(default)]
900 prefix: Option<SpannedKv<String>>,
901
902 #[darling(default)]
903 exact_prefix: Option<SpannedKv<String>>,
904
905 #[darling(default)]
906 flags: FlagsList,
907}
908
909#[derive(Debug)]
911pub(crate) struct SpannedKv<T> {
912 pub(crate) key_span: Span,
913 #[allow(dead_code)]
914 pub(crate) value_span: Span,
915 pub(crate) value: T,
916}
917
918impl<T: FromMeta> FromMeta for SpannedKv<T> {
919 fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
920 let value = T::from_meta(item).map_err(|e| e.with_span(item))?;
921 let (key_span, value_span) = match item {
922 syn::Meta::NameValue(nv) => (nv.path.span(), nv.value.span()),
923 _ => return Err(darling::Error::custom("expected a key value pair").with_span(item)),
924 };
925
926 Ok(SpannedKv {
927 key_span,
928 value_span,
929 value,
930 })
931 }
932}
933
934pub(crate) fn parse_metric_fields(
935 fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
936) -> Result<Vec<MetricsField>> {
937 let mut parsed_fields = vec![];
938 let mut errors = darling::Error::accumulator();
939
940 for (i, field) in fields.iter().enumerate() {
941 let i = syn::Index::from(i);
942 let (ident, name, span) = match &field.ident {
943 Some(ident) => (quote! { #ident }, Some(ident.to_string()), ident.span()),
944 None => (quote! { #i }, None, field.ty.span()),
945 };
946
947 let attrs = match errors
948 .handle(RawMetricsFieldAttrs::from_field(field).and_then(|attr| attr.validate()))
949 {
950 Some(attrs) => attrs,
951 None => {
952 continue;
953 }
954 };
955
956 parsed_fields.push(MetricsField {
957 ident,
958 name,
959 span,
960 ty: field.ty.clone(),
961 vis: field.vis.clone(),
962 external_attrs: clean_attrs(&field.attrs),
963 attrs,
964 });
965 }
966
967 errors.finish()?;
968
969 Ok(parsed_fields)
970}
971
972fn cannot_combine_error(existing: &str, new: &str, new_span: Span) -> darling::Error {
973 darling::Error::custom(format!("Cannot combine `{existing}` with `{new}`")).with_span(&new_span)
974}
975
976fn set_exclusive<T>(
978 new: impl Fn(Span) -> T,
979 name: &'static str,
980 existing: Option<(T, &'static str)>,
981 flag: &Flag,
982) -> darling::Result<Option<(T, &'static str)>> {
983 match (flag.is_present(), &existing) {
984 (true, Some((_, other))) => Err(cannot_combine_error(other, name, flag.span())),
985 (true, None) => Ok(Some((new(flag.span()), name))),
986 _ => Ok(existing),
987 }
988}
989
990fn get_field_option<'a, T>(
992 field_name: &'static str,
993 existing: &Option<(MetricsFieldKind, &'static str)>,
994 span: &'a Option<SpannedKv<T>>,
995) -> darling::Result<Option<&'a T>> {
996 match (span, &existing) {
997 (Some(input), Some((_, other))) => {
998 Err(cannot_combine_error(other, field_name, input.key_span))
999 }
1000 (Some(v), None) => Ok(Some(&v.value)),
1001 _ => Ok(None),
1002 }
1003}
1004
1005fn get_field_flag(
1007 field_name: &'static str,
1008 existing: &Option<(MetricsFieldKind, &'static str)>,
1009 flag: &Flag,
1010) -> darling::Result<Option<Span>> {
1011 match (flag.is_present(), &existing) {
1012 (true, Some((_, other))) => Err(cannot_combine_error(other, field_name, flag.span())),
1013 (true, None) => Ok(Some(flag.span())),
1014 _ => Ok(None),
1015 }
1016}
1017
1018impl RawMetricsFieldAttrs {
1019 fn validate(self) -> darling::Result<MetricsFieldAttrs> {
1020 let mut out: Option<(MetricsFieldKind, &'static str)> = None;
1021 out = set_exclusive(
1022 |span| MetricsFieldKind::Flatten { span, prefix: None },
1023 "flatten",
1024 out,
1025 &self.flatten,
1026 )?;
1027 out = set_exclusive(
1028 MetricsFieldKind::FlattenEntry,
1029 "flatten_entry",
1030 out,
1031 &self.flatten_entry,
1032 )?;
1033 out = set_exclusive(
1034 MetricsFieldKind::Timestamp,
1035 "timestamp",
1036 out,
1037 &self.timestamp,
1038 )?;
1039 out = set_exclusive(MetricsFieldKind::Ignore, "ignore", out, &self.ignore)?;
1040
1041 let name = self.name.map(validate_name).transpose()?;
1042 let name = get_field_option("name", &out, &name)?;
1043 let unit = get_field_option("unit", &out, &self.unit)?;
1044 let format = get_field_option("format", &out, &self.format)?;
1045 let sample_group = get_field_flag("sample_group", &out, &self.sample_group)?;
1046 let close = !self.no_close.is_present();
1047 if let (false, Some((MetricsFieldKind::Ignore(span), _))) = (close, &out) {
1048 return Err(cannot_combine_error("no_close", "ignore", *span));
1049 }
1050
1051 let prefix = Prefix::from_inflectable_and_exact(
1052 &self.prefix,
1053 &self.exact_prefix,
1054 PrefixLevel::Field,
1055 )?;
1056 if let Some(prefix_) = prefix {
1057 match &mut out {
1058 Some((MetricsFieldKind::Flatten { prefix, .. }, _)) => {
1059 *prefix = Some(prefix_.into_inner());
1060 }
1061 _ => {
1062 return Err(
1063 darling::Error::custom("prefix can only be used with `flatten`")
1064 .with_span(&prefix_.span()),
1065 );
1066 }
1067 }
1068 }
1069
1070 if !self.flags.0.is_empty()
1072 && let Some((
1073 MetricsFieldKind::Flatten { span, .. }
1074 | MetricsFieldKind::FlattenEntry(span)
1075 | MetricsFieldKind::Timestamp(span)
1076 | MetricsFieldKind::Ignore(span),
1077 _,
1078 )) = &out
1079 {
1080 return Err(darling::Error::custom(
1081 "flags(...) is not yet supported on flatten, flatten_entry, timestamp, or ignore fields.",
1082 )
1083 .with_span(span));
1084 }
1085
1086 Ok(MetricsFieldAttrs {
1087 close,
1088 kind: match out {
1089 Some((out, _)) => out,
1090 None => MetricsFieldKind::Field {
1091 sample_group,
1092 name: name.cloned(),
1093 unit: unit.cloned(),
1094 format: format.cloned(),
1095 },
1096 },
1097 flags: self.flags.0,
1098 })
1099 }
1100}
1101
1102fn validate_name(name: SpannedKv<String>) -> darling::Result<SpannedKv<String>> {
1103 match validate_name_inner(&name.value) {
1104 Ok(_) => Ok(name),
1105 Err(msg) => Err(darling::Error::custom(msg).with_span(&name.value_span)),
1106 }
1107}
1108
1109fn validate_name_inner(name: &str) -> std::result::Result<(), &'static str> {
1110 if name.is_empty() {
1111 return Err("invalid name: name field must not be empty");
1112 }
1113
1114 if name.contains(' ') {
1115 return Err("invalid name: name must not contain spaces");
1116 }
1117 Ok(())
1118}
1119
1120#[derive(Debug, Clone)]
1121struct MetricsFieldAttrs {
1122 close: bool,
1123 kind: MetricsFieldKind,
1124 flags: Vec<syn::Path>,
1125}
1126
1127pub(crate) struct MetricsField {
1128 pub(crate) vis: Visibility,
1129 pub(crate) ident: Ts2,
1130 pub(crate) name: Option<String>,
1131 pub(crate) span: Span,
1132 pub(crate) ty: Type,
1133 pub(crate) external_attrs: Vec<Attribute>,
1134 pub(crate) attrs: MetricsFieldAttrs,
1135}
1136
1137impl MetricsField {
1138 pub(crate) fn cfg_attrs(&self) -> impl Iterator<Item = &Attribute> {
1142 self.external_attrs
1143 .iter()
1144 .filter(|a| a.path().is_ident("cfg") || a.path().is_ident("cfg_attr"))
1145 }
1146}
1147
1148impl MetricsField {
1149 fn core_field(&self, is_named: bool) -> Ts2 {
1150 let MetricsField {
1151 ref external_attrs,
1152 ref ident,
1153 ref ty,
1154 ref vis,
1155 ..
1156 } = *self;
1157 let field = if is_named {
1158 quote! { #ident: #ty }
1159 } else {
1160 quote! { #ty }
1161 };
1162 quote! { #(#external_attrs)* #vis #field }
1163 }
1164
1165 fn entry_field(&self, named: bool) -> Option<Ts2> {
1166 if let MetricsFieldKind::Ignore(_span) = self.attrs.kind {
1167 return None;
1168 }
1169 let MetricsField {
1170 ident, ty, span, ..
1171 } = self;
1172 let mut base_type = if self.attrs.close {
1173 quote_spanned! { *span=> <#ty as metrique::CloseValue>::Closed }
1174 } else {
1175 quote_spanned! { *span=>#ty }
1176 };
1177 if let Some(expr) = self.unit() {
1178 base_type = quote_spanned! { expr.span()=>
1179 <#base_type as ::metrique::unit::AttachUnit>::Output<#expr>
1180 }
1181 }
1182 let inner = if named {
1183 quote! { #ident: #base_type }
1184 } else {
1185 quote! { #base_type }
1186 };
1187 let cfg_attrs = self.cfg_attrs();
1188 Some(quote_spanned! { *span=>
1189 #(#cfg_attrs)*
1190 #[deprecated(note = "these fields will become private in a future release. To introspect an entry, use `metrique::writer::test_util::test_entry`")]
1191 #[doc(hidden)]
1192 #inner
1193 })
1194 }
1195
1196 fn unit(&self) -> Option<&syn::Path> {
1197 match &self.attrs.kind {
1198 MetricsFieldKind::Field { unit, .. } => unit.as_ref(),
1199 _ => None,
1200 }
1201 }
1202
1203 pub(crate) fn close_value(&self, ownership_kind: OwnershipKind) -> Ts2 {
1204 let ident = &self.ident;
1205 let span = self.span;
1206 let field_expr = match ownership_kind {
1207 OwnershipKind::ByValue => quote_spanned! {span=> __metrique_self_expr!().#ident },
1208 OwnershipKind::ByRef => quote_spanned! {span=> &__metrique_self_expr!().#ident },
1209 };
1210 self.close_field_expr(field_expr)
1211 }
1212
1213 pub(crate) fn close_field_expr(&self, field_expr: Ts2) -> Ts2 {
1214 let ident = &self.ident;
1215 let span = self.span;
1216 let base = if self.attrs.close {
1217 quote_spanned! {span=> metrique::CloseValue::close(#field_expr) }
1218 } else {
1219 field_expr
1220 };
1221
1222 let base = if let Some(unit) = self.unit() {
1223 quote_spanned! { unit.span() =>
1224 #base.into()
1225 }
1226 } else {
1227 base
1228 };
1229
1230 let cfg_attrs = self.cfg_attrs();
1231 quote! { #(#cfg_attrs)* #ident: #base }
1232 }
1233}
1234
1235pub(crate) struct TupleData {
1236 pub(crate) ty: syn::Type,
1237 pub(crate) kind: MetricsFieldKind,
1238 pub(crate) close: bool,
1239}
1240
1241pub(crate) fn entry_type(ty: &syn::Type, close: bool, span: proc_macro2::Span) -> Ts2 {
1243 if close {
1244 quote::quote_spanned! { span=> <#ty as metrique::CloseValue>::Closed }
1245 } else {
1246 quote::quote_spanned! { span=> #ty }
1247 }
1248}
1249
1250pub(crate) enum PrefixLevel {
1251 Root,
1252 Field,
1253}
1254
1255#[derive(Debug, Clone)]
1256pub(crate) enum Prefix {
1257 Inflectable { prefix: String },
1258 Exact(String),
1259}
1260
1261impl Prefix {
1262 pub(crate) fn apply(&self, base: &str, name_style: NameStyle) -> String {
1264 match self {
1265 Prefix::Exact(exact_prefix) => {
1266 format!("{}{}", exact_prefix, name_style.apply(base))
1267 }
1268 Prefix::Inflectable { prefix } => {
1269 let prefixed = format!("{}{}", prefix, base);
1270 name_style.apply(&prefixed)
1271 }
1272 }
1273 }
1274
1275 fn inflected_prefix_message(prefix: &str, c: char) -> String {
1276 let warning_text = if name_contains_dot(prefix) {
1277 " '.' used to be allowed in `prefix` but is now forbidden."
1278 } else {
1279 ""
1280 };
1281 let prefix_fixed: String = prefix
1282 .chars()
1283 .map(|c| if !c.is_alphanumeric() { '-' } else { c })
1284 .collect();
1285 format!(
1286 "You cannot use the character {c:?} with `prefix`. `prefix` will \"inflect\" to match the name scheme specified by `rename_all`. For example, \
1287 it will change all delimiters to `-` for kebab case). If you want to match namestyle, use `prefix = {prefix_fixed:?}`. If you want to preserve {c:?} \
1288 in the final metric name use `exact_prefix = {prefix:?}.{warning_text}"
1289 )
1290 }
1291
1292 fn prefix_should_end_with_delimiter_message(prefix: &str) -> String {
1293 let delimiter = if prefix.contains('-') { '-' } else { '_' };
1294 let prefix_fixed = format!("{prefix}{delimiter}");
1295 format!(
1296 "The root-level prefix `{prefix:?}` must end with a delimiter. Use `prefix = {prefix_fixed:?}`, which inflects \
1297 correctly in all inflections"
1298 )
1299 }
1300
1301 fn from_inflectable_and_exact(
1302 inflectable: &Option<SpannedKv<String>>,
1303 exact: &Option<SpannedKv<String>>,
1304 level: PrefixLevel,
1305 ) -> darling::Result<Option<SpannedValue<Self>>> {
1306 match (inflectable, exact) {
1307 (Some(prefix), None) => {
1308 if let Some(c) = name_contains_uninflectables(&prefix.value) {
1309 Err(
1310 darling::Error::custom(Self::inflected_prefix_message(&prefix.value, c))
1311 .with_span(&prefix.key_span),
1312 )
1313 } else if let PrefixLevel::Root = level
1314 && !name_ends_with_delimiter(&prefix.value)
1315 {
1316 Err(
1317 darling::Error::custom(Self::prefix_should_end_with_delimiter_message(
1318 &prefix.value,
1319 ))
1320 .with_span(&prefix.key_span),
1321 )
1322 } else {
1323 Ok(Some(SpannedValue::new(
1324 Self::Inflectable {
1325 prefix: prefix.value.clone(),
1326 },
1327 prefix.key_span,
1328 )))
1329 }
1330 }
1331 (None, Some(p)) => Ok(Some(SpannedValue::new(
1332 Prefix::Exact(p.value.clone()),
1333 p.key_span,
1334 ))),
1335 (None, None) => Ok(None),
1336 (Some(inflectable), Some(_)) => Err(cannot_combine_error(
1337 "prefix",
1338 "exact_prefix",
1339 inflectable.key_span,
1340 )),
1341 }
1342 }
1343
1344 pub(crate) fn append_to(
1348 &self,
1349 ns: &proc_macro2::TokenStream,
1350 span: proc_macro2::Span,
1351 ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
1352 match self {
1353 Prefix::Inflectable { prefix } => {
1354 crate::entry_impl::make_inflect_prefix(ns, prefix, span)
1355 }
1356 Prefix::Exact(exact_prefix) => {
1357 crate::entry_impl::make_exact_prefix(ns, exact_prefix, span)
1358 }
1359 }
1360 }
1361}
1362
1363#[derive(Debug, Clone)]
1364enum MetricsFieldKind {
1365 Ignore(Span),
1366 Flatten {
1367 span: Span,
1368 prefix: Option<Prefix>,
1369 },
1370 FlattenEntry(Span),
1371 Timestamp(Span),
1372 Field {
1373 unit: Option<syn::Path>,
1374 name: Option<String>,
1375 format: Option<syn::Path>,
1376 sample_group: Option<Span>,
1377 },
1378}
1379
1380#[allow(unused)]
1385fn proc_macro_warning(span: Span, warning: &str) -> Ts2 {
1386 quote_spanned! {span=>
1387 const _: () = {
1388 #[deprecated(note=#warning)]
1389 const _W: () = ();
1390 _W
1391 };
1392 }
1393}
1394
1395fn parse_root_attrs(attr: TokenStream) -> Result<RootAttributes> {
1396 let nested_meta = NestedMeta::parse_meta_list(attr.into())?;
1397 Ok(RawRootAttributes::from_list(&nested_meta)?.validate()?)
1398}
1399
1400fn generate_metrics(root_attributes: RootAttributes, input: DeriveInput) -> Result<Ts2> {
1401 if input
1403 .attrs
1404 .iter()
1405 .any(|attr| attr.path().is_ident("aggregate"))
1406 {
1407 return Err(Error::new_spanned(
1408 &input,
1409 "#[aggregate] must be placed before #[metrics], not after",
1410 ));
1411 }
1412
1413 let output = match root_attributes.mode {
1414 MetricMode::RootEntry | MetricMode::Subfield | MetricMode::SubfieldOwned => {
1415 match &input.data {
1416 Data::Struct(data_struct) => {
1417 if root_attributes.tag.is_some() {
1418 return Err(Error::new_spanned(
1419 &input,
1420 "`tag` attribute is only supported on entry enums",
1421 ));
1422 }
1423 let fields = match &data_struct.fields {
1424 Fields::Named(fields_named) => &fields_named.named,
1425 _ => {
1426 return Err(Error::new_spanned(
1427 &input,
1428 "Only named fields are supported",
1429 ));
1430 }
1431 };
1432 structs::generate_metrics_for_struct(root_attributes, &input, fields)?
1433 }
1434 Data::Enum(data_enum) => {
1435 let variants =
1436 enums::parse_enum_variants(&data_enum.variants, enums::VariantMode::Entry)?;
1437 enums::generate_metrics_for_enum(root_attributes, &input, &variants)?
1438 }
1439 Data::Union(_) => {
1440 return Err(Error::new_spanned(
1441 &input,
1442 "Only structs and enums are supported for entries",
1443 ));
1444 }
1445 }
1446 }
1447 MetricMode::Value => match &input.data {
1448 Data::Struct(data_struct) => {
1449 let fields = match &data_struct.fields {
1450 Fields::Named(fields_named) => &fields_named.named,
1451 Fields::Unnamed(fields_unnamed) => &fields_unnamed.unnamed,
1452 _ => {
1453 return Err(Error::new_spanned(
1454 &input,
1455 "Only named fields are supported",
1456 ));
1457 }
1458 };
1459 structs::generate_metrics_for_struct(root_attributes, &input, fields)?
1460 }
1461 _ => {
1462 return Err(Error::new_spanned(
1463 &input,
1464 "Only structs are supported with value, use value(string) with enums",
1465 ));
1466 }
1467 },
1468 MetricMode::ValueString => {
1469 let variants = match &input.data {
1470 Data::Enum(data_enum) => &data_enum.variants,
1471 _ => {
1472 return Err(Error::new_spanned(
1473 &input,
1474 "Only enums are supported with value(string)",
1475 ));
1476 }
1477 };
1478 let variants = enums::parse_enum_variants(variants, enums::VariantMode::ValueString)?;
1479 enums::generate_metrics_for_enum(root_attributes, &input, &variants)?
1480 }
1481 };
1482
1483 if std::env::var("MACRO_DEBUG").is_ok() {
1484 eprintln!("{}", &output);
1485 }
1486
1487 Ok(output)
1488}
1489
1490fn with_static_lifetimes(ident: &Ident, generics: &Generics) -> Ts2 {
1492 if generics.params.is_empty() {
1493 return quote! { #ident };
1494 }
1495
1496 let args = generics.params.iter().map(|param| match param {
1497 GenericParam::Lifetime(_) => quote! { 'static },
1498 GenericParam::Type(ty) => {
1499 let ident = &ty.ident;
1500 quote! { #ident }
1501 }
1502 GenericParam::Const(c) => {
1503 let ident = &c.ident;
1504 quote! { #ident }
1505 }
1506 });
1507
1508 quote! { #ident<#(#args),*> }
1509}
1510
1511pub(crate) fn generate_on_drop_wrapper(
1513 vis: &Visibility,
1514 guard: &Ident,
1515 inner: &Ident,
1516 target: &Ident,
1517 handle: &Ident,
1518 generics: &Generics,
1519) -> Ts2 {
1520 let inner_str = inner.to_string();
1521 let guard_str = guard.to_string();
1522
1523 let (_impl_generics, _, where_clause) = generics.split_for_impl();
1524 let inner_static = with_static_lifetimes(inner, generics);
1525 let target_static = with_static_lifetimes(target, generics);
1526
1527 quote! {
1528 #[doc = concat!("Metrics guard returned from [`", #inner_str, "::append_on_drop`], closes the entry and appends the metrics to a sink when dropped.")]
1529 #vis type #guard<Q = ::metrique::DefaultSink> = ::metrique::AppendAndCloseOnDrop<#inner_static, Q>;
1530
1531 #[doc = concat!("Metrics handle returned from [`", #guard_str, "::handle`], similar to an `Arc<", #guard_str, ">`.")]
1532 #vis type #handle<Q = ::metrique::DefaultSink> = ::metrique::AppendAndCloseOnDropHandle<#inner_static, Q>;
1533
1534 impl #inner_static #where_clause {
1535 #[doc = "Creates an AppendAndCloseOnDrop that will be automatically appended to `sink` on drop."]
1536 #vis fn append_on_drop<Q: ::metrique::writer::EntrySink<::metrique::RootEntry<#target_static>> + Send + Sync + 'static>(self, sink: Q) -> #guard<Q> {
1537 ::metrique::append_and_close(self, sink)
1538 }
1539 }
1540 }
1541}
1542
1543fn generate_close_value_impls(
1544 root_attrs: &RootAttributes,
1545 base_ty: &Ident,
1546 closed_ty: &Ident,
1547 generics: &syn::Generics,
1548 impl_body: Ts2,
1549) -> Ts2 {
1550 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1551
1552 let (metrics_struct_ty, proxy_impl) = match root_attrs.ownership_kind() {
1553 OwnershipKind::ByValue => (quote!(#base_ty #ty_generics), quote!()),
1554 OwnershipKind::ByRef => (
1555 quote!(&'_ #base_ty #ty_generics),
1556 quote!(impl #impl_generics metrique::CloseValue for #base_ty #ty_generics #where_clause {
1558 type Closed = #closed_ty #ty_generics;
1559 fn close(self) -> Self::Closed {
1560 <&Self>::close(&self)
1561 }
1562 }),
1563 ),
1564 };
1565
1566 let close_fn = quote! {
1567 fn close(self) -> Self::Closed {
1568 macro_rules! __metrique_self_expr {
1572 () => {
1573 self
1574 };
1575 }
1576 #impl_body
1577 }
1578 };
1579
1580 quote! {
1581 impl #impl_generics metrique::CloseValue for #metrics_struct_ty #where_clause {
1582 type Closed = #closed_ty #ty_generics;
1583 #close_fn
1584 }
1585
1586 #proxy_impl
1587 }
1588}
1589
1590pub(crate) fn clean_attrs(attr: &[Attribute]) -> Vec<Attribute> {
1591 attr.iter()
1592 .filter(|attr| !attr.path().is_ident("metrics"))
1593 .cloned()
1594 .collect()
1595}
1596
1597fn clean_base_adt(input: &DeriveInput) -> Ts2 {
1607 let adt_name = &input.ident;
1608 let vis = &input.vis;
1609 let generics = &input.generics;
1610
1611 let filtered_attrs = clean_attrs(&input.attrs);
1613 match &input.data {
1614 Data::Struct(data_struct) => match &data_struct.fields {
1615 Fields::Named(fields_named) => {
1616 structs::clean_base_struct(vis, adt_name, generics, filtered_attrs, fields_named)
1617 }
1618 Fields::Unnamed(fields_unnamed) => structs::clean_base_unnamed_struct(
1619 vis,
1620 adt_name,
1621 generics,
1622 filtered_attrs,
1623 fields_unnamed,
1624 ),
1625 _ => input.to_token_stream(),
1628 },
1629 Data::Enum(data_enum) => {
1630 if let Ok(variants) = enums::parse_enum_variants(
1631 &data_enum.variants,
1632 enums::VariantMode::SkipAttributeParsing,
1633 ) {
1634 enums::generate_base_enum(adt_name, vis, generics, &filtered_attrs, &variants)
1635 } else {
1636 input.to_token_stream()
1637 }
1638 }
1639 _ => input.to_token_stream(),
1640 }
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645 use darling::FromMeta;
1646 use insta::assert_snapshot;
1647 use proc_macro2::TokenStream as Ts2;
1648 use quote::quote;
1649 use syn::{parse_quote, parse2};
1650
1651 use crate::RawRootAttributes;
1652
1653 fn metrics_impl(input: Ts2, attrs: Ts2) -> Ts2 {
1656 let input = syn::parse2(input).unwrap();
1657 let meta: syn::Meta = syn::parse2(attrs).unwrap();
1658 let root_attrs = RawRootAttributes::from_meta(&meta)
1659 .unwrap()
1660 .validate()
1661 .unwrap();
1662 super::generate_metrics(root_attrs, input).unwrap()
1663 }
1664
1665 fn metrics_impl_string(input: Ts2, attrs: Ts2) -> String {
1666 let output = metrics_impl(input, attrs);
1667
1668 match parse2::<syn::File>(output.clone()) {
1670 Ok(file) => prettyplease::unparse(&file),
1671 Err(_) => {
1672 output.to_string()
1674 }
1675 }
1676 }
1677
1678 #[test]
1679 fn test_darling_root_attrs() {
1680 use darling::FromMeta;
1681 RawRootAttributes::from_meta(&parse_quote! {
1682 metrics(
1683 rename_all = "PascalCase",
1684 emf::dimension_sets = [["bar"]]
1685 )
1686 })
1687 .unwrap()
1688 .validate()
1689 .unwrap();
1690 }
1691
1692 #[test]
1693 fn test_simple_metrics_struct() {
1694 let input = quote! {
1695 struct RequestMetrics {
1696 operation: &'static str,
1697 number_of_ducks: usize
1698 }
1699 };
1700
1701 let parsed_file = metrics_impl_string(input, quote!(metrics()));
1702 assert_snapshot!("simple_metrics_struct", parsed_file);
1703 }
1704
1705 #[test]
1706 fn test_sample_group_metrics_struct() {
1707 let input = quote! {
1708 struct RequestMetrics {
1709 #[metrics(sample_group)]
1710 operation: &'static str,
1711 number_of_ducks: usize
1712 }
1713 };
1714
1715 let parsed_file = metrics_impl_string(input, quote!(metrics()));
1716 assert_snapshot!("sample_group_metrics_struct", parsed_file);
1717 }
1718
1719 #[test]
1720 fn test_simple_metrics_value_struct() {
1721 let input = quote! {
1722 struct RequestValue {
1723 #[metrics(ignore)]
1724 ignore: u32,
1725 value: u32,
1726 }
1727 };
1728
1729 let parsed_file = metrics_impl_string(input, quote!(metrics(value)));
1730 assert_snapshot!("simple_metrics_value_struct", parsed_file);
1731 }
1732
1733 #[test]
1734 fn test_sample_group_metrics_value_struct() {
1735 let input = quote! {
1736 struct RequestValue {
1737 #[metrics(ignore)]
1738 ignore: u32,
1739 value: &'static str,
1740 }
1741 };
1742
1743 let parsed_file = metrics_impl_string(input, quote!(metrics(value, sample_group)));
1744 assert_snapshot!("sample_group_metrics_value_struct", parsed_file);
1745 }
1746
1747 #[test]
1748 fn test_simple_metrics_value_unnamed_struct() {
1749 let input = quote! {
1750 struct RequestValue(
1751 #[metrics(ignore)]
1752 u32,
1753 u32);
1754 };
1755
1756 let parsed_file = metrics_impl_string(input, quote!(metrics(value)));
1757 assert_snapshot!("simple_metrics_value_unnamed_struct", parsed_file);
1758 }
1759
1760 #[test]
1761 fn test_simple_metrics_enum() {
1762 let input = quote! {
1763 enum Foo {
1764 Bar
1765 }
1766 };
1767
1768 let parsed_file = metrics_impl_string(input, quote!(metrics(value(string))));
1769 assert_snapshot!("simple_metrics_enum", parsed_file);
1770 }
1771
1772 #[test]
1773 fn test_exact_prefix_struct() {
1774 let input = quote! {
1775 struct RequestMetrics {
1776 operation: &'static str,
1777 number_of_ducks: usize
1778 }
1779 };
1780
1781 let parsed_file = metrics_impl_string(input, quote!(metrics(exact_prefix = "API@")));
1782 assert_snapshot!("exact_prefix_struct", parsed_file);
1783 }
1784
1785 #[test]
1786 fn test_field_exact_prefix_struct() {
1787 let input = quote! {
1788 struct RequestMetrics {
1789 #[metrics(flatten, exact_prefix = "API@")]
1790 nested: NestedMetrics,
1791 operation: &'static str
1792 }
1793 };
1794
1795 let parsed_file = metrics_impl_string(input, quote!(metrics()));
1796 assert_snapshot!("field_exact_prefix_struct", parsed_file);
1797 }
1798
1799 #[test]
1800 fn test_aggregate_after_metrics_error() {
1801 let input = quote! {
1802 #[metrics]
1803 #[aggregate]
1804 struct ApiCall {
1805 latency: Duration,
1806 }
1807 };
1808
1809 let input = syn::parse2(input).unwrap();
1810 let root_attrs = RawRootAttributes::from_meta(&parse_quote!(metrics()))
1811 .unwrap()
1812 .validate()
1813 .unwrap();
1814 let result = super::generate_metrics(root_attrs, input);
1815 assert!(result.is_err());
1816 let err = result.unwrap_err();
1817 assert!(
1818 err.to_string()
1819 .contains("#[aggregate] must be placed before #[metrics]")
1820 );
1821 }
1822
1823 #[test]
1824 fn test_metrics_with_lifetime() {
1825 let input = quote! {
1826 struct Foo<'a> {
1827 a: &'a str,
1828 b: usize
1829 }
1830 };
1831
1832 let parsed_file = metrics_impl_string(input, quote!(metrics()));
1833 assert_snapshot!("metrics_with_lifetime", parsed_file);
1834 }
1835
1836 #[test]
1837 fn test_metrics_with_cow_lifetime() {
1838 let input = quote! {
1839 struct Foo<'a> {
1840 a: Cow<'a, str>,
1841 b: usize
1842 }
1843 };
1844
1845 let parsed_file = metrics_impl_string(input, quote!(metrics()));
1846 assert_snapshot!("metrics_with_cow_lifetime", parsed_file);
1847 }
1848
1849 #[test]
1850 fn test_field_inflectable_prefix_struct() {
1851 let input = quote! {
1852 struct RequestMetrics {
1853 #[metrics(flatten, prefix = "api_")]
1854 nested: NestedMetrics,
1855 operation: &'static str
1856 }
1857 };
1858
1859 let parsed_file = metrics_impl_string(input, quote!(metrics()));
1860 assert_snapshot!("field_inflectable_prefix_struct", parsed_file);
1861 }
1862
1863 #[test]
1864 fn test_entry_enum() {
1865 let nested = metrics_impl_string(
1866 quote! {
1867 #[metrics(subfield)]
1868 struct Nested {
1869 value: u32,
1870 }
1871 },
1872 quote!(metrics(subfield)),
1873 );
1874 let status = metrics_impl_string(
1875 quote! {
1876 #[metrics(subfield)]
1877 enum Status {
1878 Active {
1879 count: u32,
1880 #[metrics(unit = metrique::writer::unit::Millisecond)]
1881 latency: u64,
1882 },
1883 Pending(#[metrics(flatten)] Nested),
1884 Multi(
1885 #[metrics(flatten)] Nested,
1886 #[metrics(ignore)] u32,
1887 ),
1888 }
1889 },
1890 quote!(metrics(subfield)),
1891 );
1892 let root = metrics_impl_string(
1893 quote! {
1894 enum Operation {
1895 Read { bytes: u64 },
1896 Write(#[metrics(flatten)] Nested),
1897 }
1898 },
1899 quote!(metrics()),
1900 );
1901
1902 let parsed_file = format!("{}\n{}\n{}", nested, status, root);
1903 assert_snapshot!("entry_enum", parsed_file);
1904 }
1905
1906 #[test]
1907 fn test_subfield_struct() {
1908 let input = quote! {
1909 #[metrics(subfield)]
1910 struct NestedMetrics {
1911 counter: u32,
1912 }
1913 };
1914
1915 let parsed_file = metrics_impl_string(input, quote!(metrics(subfield)));
1916 assert_snapshot!("subfield_struct", parsed_file);
1917 }
1918
1919 #[test]
1920 fn test_sample_group_entry_enum() {
1921 let operation = metrics_impl_string(
1922 quote! {
1923 #[metrics(value(string))]
1924 enum Operation {
1925 Read,
1926 Write,
1927 }
1928 },
1929 quote!(metrics(value(string))),
1930 );
1931 let metadata = metrics_impl_string(
1932 quote! {
1933 #[metrics(subfield)]
1934 struct Metadata {
1935 #[metrics(sample_group)]
1936 operation: Operation,
1937 request_id: String,
1938 }
1939 },
1940 quote!(metrics(subfield)),
1941 );
1942 let result = metrics_impl_string(
1943 quote! {
1944 enum RequestResult {
1945 Success {
1946 #[metrics(sample_group)]
1947 operation: Operation,
1948 bytes: usize,
1949 },
1950 Error {
1951 #[metrics(sample_group)]
1952 operation: Operation,
1953 error_code: u32,
1954 },
1955 Timeout(#[metrics(flatten)] Metadata),
1956 Cancelled(
1957 #[metrics(flatten)] Metadata,
1958 #[metrics(flatten_entry, no_close)] StatusEntry
1959 ),
1960 }
1961 },
1962 quote!(metrics()),
1963 );
1964
1965 let parsed_file = format!("{}\n{}\n{}", operation, metadata, result);
1966 assert_snapshot!("sample_group_entry_enum", parsed_file);
1967 }
1968
1969 #[test]
1970 fn test_entry_enum_tag() {
1971 let nested = metrics_impl_string(
1972 quote! {
1973 #[metrics(subfield)]
1974 struct Nested {
1975 value: u32,
1976 }
1977 },
1978 quote!(metrics(subfield)),
1979 );
1980 let root = metrics_impl_string(
1981 quote! {
1982 #[metrics(tag(name = "operation"))]
1983 enum Operation {
1984 Read { bytes: usize },
1985 Write(#[metrics(flatten)] Nested),
1986 }
1987 },
1988 quote!(metrics(tag(name = "operation"))),
1989 );
1990
1991 let parsed_file = format!("{}\n{}", nested, root);
1992 assert_snapshot!("entry_enum_tag", parsed_file);
1993 }
1994
1995 #[test]
1996 fn test_entry_enum_tag_with_sample_group() {
1997 let root = metrics_impl_string(
1998 quote! {
1999 #[metrics(tag(name = "operation", sample_group))]
2000 enum Operation {
2001 Read { bytes: usize },
2002 }
2003 },
2004 quote!(metrics(tag(name = "operation", sample_group))),
2005 );
2006
2007 assert_snapshot!("entry_enum_tag_sample_group", root);
2008 }
2009
2010 #[test]
2011 fn test_debug_derive_passthrough_struct() {
2012 let input = quote! {
2013 #[derive(Debug, Clone)]
2014 struct Metrics {
2015 field: usize,
2016 }
2017 };
2018
2019 let parsed_file = metrics_impl_string(input, quote!(metrics()));
2020 assert_snapshot!("debug_derive_passthrough_struct", parsed_file);
2021 }
2022
2023 #[test]
2024 fn test_debug_derive_passthrough_enum() {
2025 let input = quote! {
2026 #[derive(Debug)]
2027 enum Operation {
2028 Read,
2029 Write,
2030 }
2031 };
2032
2033 let parsed_file = metrics_impl_string(input, quote!(metrics(value(string))));
2034 assert_snapshot!("debug_derive_passthrough_enum", parsed_file);
2035 }
2036}