-
Notifications
You must be signed in to change notification settings - Fork 34
Allow specifying result labels for enum variants #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
6ce2de0
Allow specifying result labels for enum variants
2f913a6
Add variant matchers to the calls
3352b7c
Add tests for ErrorLabels using TryBuild
1a199d8
Add documentation and update tests
5cf0228
Merge branch 'main' of github.com:autometrics-dev/autometrics-rs into…
e3381ce
Review: rename error_labels to result_labels
78f8588
Simplify code
8c8175e
Merge branch 'main' of github.com:autometrics-dev/autometrics-rs into…
1b3931d
Add reference test
2175ccc
WIP: Get ResultLabels to work outside of results
dba8214
Use spez to determine result labels
3df90d4
Merge branch 'main' of github.com:autometrics-dev/autometrics-rs into…
4621282
Try switching match arms
6b1869a
Annotate temporary result type when possible
4e67714
Add comments
b88cff3
Review
fccbe70
Merge branch 'main' into ok_variants_within_error_enums
gagbo f5182a3
Merge branch 'main' of github.com:autometrics-dev/autometrics-rs into…
2ff0388
Merge branch 'main' into ok_variants_within_error_enums
gagbo a0acdc8
Add changelog
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| wip | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| //! The definition of the ErrorLabels derive macro, that allows to specify | ||
| //! inside an enumeration whether variants should be considered as errors or | ||
| //! successes as far as the automatic metrics are concerned. | ||
| //! | ||
| //! For example, this would allow you to put all the client-side errors in a | ||
| //! HTTP webserver (4**) as successes, since it means the handler function | ||
| //! _successfully_ rejected a bad request, and that should not affect the SLO or | ||
| //! the success rate of the function in the metrics. | ||
| //! | ||
| //! ```rust,ignore | ||
| //! #[derive(ErrorLabels)] | ||
| //! enum ServiceError { | ||
| //! // By default, the variant will be labeled as an error, | ||
| //! // so you do not need to decorate every variant | ||
| //! Database, | ||
| //! // It is possible to mention it as well of course. | ||
| //! // Only "error" and "ok" are accepted values | ||
| //! #[label(result = "error")] | ||
| //! Network, | ||
| //! #[label(result = "ok")] | ||
| //! Authentication, | ||
| //! #[label(result = "ok")] | ||
| //! Authorization, | ||
| //! } | ||
| //! ``` | ||
|
|
||
| use proc_macro2::TokenStream; | ||
| use quote::quote; | ||
| use syn::{ | ||
| punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum, DeriveInput, Error, Ident, | ||
| Lit, LitStr, Result, Variant, | ||
| }; | ||
|
|
||
| // These labels must match autometrics::ERROR_KEY and autometrics::OK_KEY, | ||
| // to avoid a dependency loop just for 2 constants we recreate these here. | ||
| const OK_KEY: &str = "ok"; | ||
| const ERROR_KEY: &str = "error"; | ||
| const RESULT_KEY: &str = "result"; | ||
| const ATTR_LABEL: &str = "label"; | ||
| const ACCEPTED_LABELS: [&str; 2] = [ERROR_KEY, OK_KEY]; | ||
|
|
||
| /// Entry point of the ErrorLabels macro | ||
| pub(crate) fn expand(input: DeriveInput) -> Result<TokenStream> { | ||
| let Data::Enum(DataEnum { | ||
| variants, | ||
| ..}) = &input.data else | ||
| { | ||
| return Err(Error::new_spanned( | ||
| input, | ||
| "ErrorLabels only works with 'Enum's.", | ||
| )) | ||
| }; | ||
| let enum_name = &input.ident; | ||
| let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); | ||
| let conditional_clauses_for_labels = conditional_label_clauses(variants, enum_name)?; | ||
|
|
||
| Ok(quote! { | ||
| #[automatically_derived] | ||
| impl #impl_generics ::autometrics::__private::GetErrorLabelFromEnum for #enum_name #ty_generics #where_clause { | ||
| fn __autometrics_get_error_label(&self) -> &'static str { | ||
| #(#conditional_clauses_for_labels)* | ||
| #ERROR_KEY | ||
| } | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| /// Build the list of match clauses for the generated code. | ||
| fn conditional_label_clauses( | ||
| variants: &Punctuated<Variant, Comma>, | ||
| enum_name: &Ident, | ||
| ) -> Result<Vec<TokenStream>> { | ||
| variants | ||
| .iter() | ||
| .map(|variant| { | ||
| let variant_name = &variant.ident; | ||
| let variant_matcher: TokenStream = match variant.fields { | ||
| syn::Fields::Named(_) => quote! { #variant_name {..} }, | ||
| syn::Fields::Unnamed(_) => quote! { #variant_name (_) }, | ||
| syn::Fields::Unit => quote! { #variant_name }, | ||
| }; | ||
| if let Some(key) = extract_label_attribute(&variant.attrs)? { | ||
| Ok(quote! [ | ||
| if ::std::matches!(self, & #enum_name :: #variant_matcher) { | ||
| return #key | ||
| } | ||
| ]) | ||
| } else { | ||
| // Let the code flow through the last value | ||
| Ok(quote! {}) | ||
| } | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
||
| /// Extract the wanted label from the annotation in the variant, if present. | ||
| /// The function looks for `#[label(result = "ok")]` kind of labels. | ||
| /// | ||
| /// ## Error cases | ||
| /// | ||
| /// The function will error out with the smallest possible span when: | ||
| /// | ||
| /// - The attribute on a variant is not a "list" type (so `#[label]` is not allowed), | ||
| /// - The key in the key value pair is not "result", as it's the only supported keyword | ||
| /// for now (so `#[label(non_existing_label = "ok")]` is not allowed), | ||
| /// - The value for the "result" label is not in the autometrics supported set (so | ||
| /// `#[label(result = "random label that will break queries")]` is not allowed) | ||
| fn extract_label_attribute(attrs: &[Attribute]) -> Result<Option<LitStr>> { | ||
| attrs | ||
| .iter() | ||
| .find_map(|att| match att.parse_meta() { | ||
| Ok(meta) => match &meta { | ||
| syn::Meta::List(list) => { | ||
| // Ignore attribute if it's not `label(...)` | ||
| if list.path.segments.len() != 1 || list.path.segments[0].ident != ATTR_LABEL { | ||
| return None; | ||
| } | ||
|
|
||
| // Only lists are allowed | ||
| let Some(syn::NestedMeta::Meta(syn::Meta::NameValue(pair))) = list.nested.first() else { | ||
| return Some(Err(Error::new_spanned( | ||
| meta, | ||
| format!("Only `{ATTR_LABEL}({RESULT_KEY} = \"RES\")` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), | ||
| ))) | ||
| }; | ||
|
|
||
| // Inside list, only 'result = ...' are allowed | ||
| if pair.path.segments.len() != 1 || pair.path.segments[0].ident != RESULT_KEY { | ||
| return Some(Err(Error::new_spanned( | ||
| pair.path.clone(), | ||
| format!("Only `{RESULT_KEY} = \"RES\"` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), | ||
| ))); | ||
| } | ||
|
|
||
| // Inside 'result = val', 'val' must be a string literal | ||
| let Lit::Str(ref lit_str) = pair.lit else { | ||
| return Some(Err(Error::new_spanned( | ||
| &pair.lit, | ||
| format!("Only {OK_KEY:?} or {ERROR_KEY:?}, as string literals, are accepted as result values"), | ||
| ))); | ||
| }; | ||
|
|
||
| // Inside 'result = val', 'val' must be one of the allowed string literals | ||
| if !ACCEPTED_LABELS.contains(&lit_str.value().as_str()) { | ||
| return Some(Err(Error::new_spanned( | ||
| lit_str, | ||
| format!("Only {OK_KEY:?} or {ERROR_KEY:?} are accepted as result values"), | ||
| ))); | ||
| } | ||
|
|
||
| Some(Ok(lit_str.clone())) | ||
| }, | ||
| _ => Some(Err(Error::new_spanned( | ||
| meta, | ||
| format!("Only `{ATTR_LABEL}({RESULT_KEY} = \"RES\")` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), | ||
| ))), | ||
| }, | ||
| Err(e) => Some(Err(Error::new_spanned( | ||
| att, | ||
| format!("could not parse the meta attribute: {e}"), | ||
| ))), | ||
| }) | ||
| .transpose() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| //! Tests for the ErrorLabels macro | ||
|
|
||
| #[test] | ||
| fn harness() { | ||
| let t = trybuild::TestCases::new(); | ||
| t.pass("tests/error_labels/pass/*.rs"); | ||
| t.compile_fail("tests/error_labels/fail/*.rs") | ||
| } |
18 changes: 18 additions & 0 deletions
18
autometrics-macros/tests/error_labels/fail/wrong_attribute.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // This test ensures that the macro fails with a readable | ||
| // error when the attribute given to one variant inside the | ||
| // enumeration is not in the correct form. | ||
| use autometrics_macros::ErrorLabels; | ||
|
|
||
| struct Inner {} | ||
|
|
||
| #[derive(ErrorLabels)] | ||
| enum MyError { | ||
| Empty, | ||
| #[label] | ||
| ClientError { | ||
| inner: Inner, | ||
| }, | ||
| ServerError(u64), | ||
| } | ||
|
|
||
| fn main() {} |
5 changes: 5 additions & 0 deletions
5
autometrics-macros/tests/error_labels/fail/wrong_attribute.stderr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| error: Only `label(result = "RES")` (RES can be "ok" or "error") is supported | ||
| --> tests/error_labels/fail/wrong_attribute.rs:11:7 | ||
| | | ||
| 11 | #[label] | ||
| | ^^^^^ |
18 changes: 18 additions & 0 deletions
18
autometrics-macros/tests/error_labels/fail/wrong_kv_attribute.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // This test ensures that the macro fails with a readable | ||
| // error when the attribute given to one variant inside the | ||
| // enumeration is not in the correct form. | ||
| use autometrics_macros::ErrorLabels; | ||
|
|
||
| struct Inner {} | ||
|
|
||
| #[derive(ErrorLabels)] | ||
| enum MyError { | ||
| Empty, | ||
| #[label = "error"] | ||
| ClientError { | ||
| inner: Inner, | ||
| }, | ||
| ServerError(u64), | ||
| } | ||
|
|
||
| fn main() {} |
5 changes: 5 additions & 0 deletions
5
autometrics-macros/tests/error_labels/fail/wrong_kv_attribute.stderr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| error: Only `label(result = "RES")` (RES can be "ok" or "error") is supported | ||
| --> tests/error_labels/fail/wrong_kv_attribute.rs:11:7 | ||
| | | ||
| 11 | #[label = "error"] | ||
| | ^^^^^^^^^^^^^^^ |
18 changes: 18 additions & 0 deletions
18
autometrics-macros/tests/error_labels/fail/wrong_result_name.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // This test ensures that the macro fails with a readable | ||
| // error when the attribute given to one variant inside the | ||
| // enumeration does not use the correct key for the label. | ||
| use autometrics_macros::ErrorLabels; | ||
|
|
||
| struct Inner {} | ||
|
|
||
| #[derive(ErrorLabels)] | ||
| enum MyError { | ||
| Empty, | ||
| #[label(unknown = "ok")] | ||
| ClientError { | ||
| inner: Inner, | ||
| }, | ||
| ServerError(u64), | ||
| } | ||
|
|
||
| fn main() {} |
5 changes: 5 additions & 0 deletions
5
autometrics-macros/tests/error_labels/fail/wrong_result_name.stderr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| error: Only `result = "RES"` (RES can be "ok" or "error") is supported | ||
| --> tests/error_labels/fail/wrong_result_name.rs:11:13 | ||
| | | ||
| 11 | #[label(unknown = "ok")] | ||
| | ^^^^^^^ |
19 changes: 19 additions & 0 deletions
19
autometrics-macros/tests/error_labels/fail/wrong_variant.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // This test ensures that the macro fails with a readable error when the | ||
| // attribute given to one variant inside the enumeration does not use one of the | ||
| // predetermined values (that would make the automatic queries fail, so the | ||
| // macros need to forbid wrong usage at compile time) | ||
| use autometrics_macros::ErrorLabels; | ||
|
|
||
| struct Inner {} | ||
|
|
||
| #[derive(ErrorLabels)] | ||
| enum MyError { | ||
| Empty, | ||
| #[label(result = "not ok")] | ||
| ClientError { | ||
| inner: Inner, | ||
| }, | ||
| ServerError(u64), | ||
| } | ||
|
|
||
| fn main() {} |
5 changes: 5 additions & 0 deletions
5
autometrics-macros/tests/error_labels/fail/wrong_variant.stderr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| error: Only "ok" or "error" are accepted as result values | ||
| --> tests/error_labels/fail/wrong_variant.rs:12:22 | ||
| | | ||
| 12 | #[label(result = "not ok")] | ||
| | ^^^^^^^^ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| //! This test uses interfaces not meant to be directly used. | ||
| //! | ||
| //! The goal here is to make sure that the macro has the effect we want. | ||
| //! autometrics (the library) is then responsible for orchestrating the | ||
| //! calls to `__autometrics_get_error_label` correctly when observing | ||
| //! function call results for the metrics. | ||
| use autometrics::__private::GetErrorLabelFromEnum; | ||
| use autometrics_macros::ErrorLabels; | ||
|
|
||
| struct Inner {} | ||
|
|
||
| #[derive(ErrorLabels)] | ||
| enum MyError { | ||
| #[label(result = "error")] | ||
| Empty, | ||
| #[label(result = "ok")] | ||
| ClientError { | ||
| inner: Inner, | ||
| }, | ||
| ServerError(u64), | ||
| } | ||
|
|
||
| fn main() { | ||
| let err = MyError::ClientError { inner: Inner {} }; | ||
| assert_eq!(err.__autometrics_get_error_label(), "ok"); | ||
|
|
||
| let err = MyError::Empty; | ||
| assert_eq!(err.__autometrics_get_error_label(), "error"); | ||
|
|
||
| let err = MyError::ServerError(502); | ||
| assert_eq!(err.__autometrics_get_error_label(), "error"); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.