-
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 all 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 |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| target | ||
| Cargo.lock | ||
| data | ||
|
|
||
| # Trybuild compilation error outputs in development phase | ||
| 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
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,163 @@ | ||
| //! The definition of the ResultLabels derive macro, see | ||
| //! autometrics::ResultLabels for more information. | ||
|
|
||
| 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 ResultLabels macro | ||
| pub(crate) fn expand(input: DeriveInput) -> Result<TokenStream> { | ||
| let variants = match &input.data { | ||
| Data::Enum(DataEnum { variants, .. }) => variants, | ||
| _ => { | ||
| return Err(Error::new_spanned( | ||
| input, | ||
| "ResultLabels 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::GetLabels for #enum_name #ty_generics #where_clause { | ||
| fn __autometrics_get_labels(&self) -> Option<&'static str> { | ||
| #conditional_clauses_for_labels | ||
| } | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| /// Build the list of match clauses for the generated code. | ||
| fn conditional_label_clauses( | ||
| variants: &Punctuated<Variant, Comma>, | ||
| enum_name: &Ident, | ||
| ) -> Result<TokenStream> { | ||
| let clauses: 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! [ | ||
| else if ::std::matches!(self, & #enum_name :: #variant_matcher) { | ||
| Some(#key) | ||
| } | ||
| ]) | ||
| } else { | ||
| // Let the code flow through the last value | ||
| Ok(quote! {}) | ||
| } | ||
| }) | ||
| .collect::<Result<Vec<_>>>()?; | ||
|
|
||
| Ok(quote! [ | ||
| if false { | ||
| None | ||
| } | ||
| #(#clauses)* | ||
| else { | ||
| None | ||
| } | ||
| ]) | ||
| } | ||
|
|
||
| /// 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 pair = match list.nested.first() { | ||
| Some(syn::NestedMeta::Meta(syn::Meta::NameValue(pair))) => pair, | ||
| _ => return Some(Err(Error::new_spanned( | ||
| meta, | ||
| format!("Only `{ATTR_LABEL}({RESULT_KEY} = \"RES\")` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), | ||
emschwartz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ))), | ||
| }; | ||
|
|
||
| // 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 = match pair.lit { | ||
| Lit::Str(ref lit_str) => lit_str, | ||
| _ => { | ||
| 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())) | ||
| }, | ||
| syn::Meta::NameValue(nv) if nv.path.segments.len() == 1 && nv.path.segments[0].ident == ATTR_LABEL => { | ||
| Some(Err(Error::new_spanned( | ||
| nv, | ||
| format!("Only `{ATTR_LABEL}({RESULT_KEY} = \"RES\")` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), | ||
| ))) | ||
| }, | ||
| syn::Meta::Path(p) if p.segments.len() == 1 && p.segments[0].ident == ATTR_LABEL => { | ||
| Some(Err(Error::new_spanned( | ||
| p, | ||
| format!("Only `{ATTR_LABEL}({RESULT_KEY} = \"RES\")` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), | ||
| ))) | ||
| }, | ||
| _ => None, | ||
| }, | ||
| 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
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.