Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions derive-encode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;

if let syn::Data::Struct(syn::DataStruct { ref fields, .. }) = ast.data {
for f in fields.iter() {
for a in &f.attrs {
if !a.path().is_ident("prometheus") {
continue;
}
match a.parse_args::<syn::Ident>() {
Ok(ident) if ident == "flatten" => {}
Ok(other) => {
let msg = format!(
"Provided attribute '{other}', but only 'flatten' is supported"
);
return quote! { compile_error!(#msg); }.into();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, instead of hand crafting the compile_error!, you could use syn::Error::new_spanned(&other, msg).to_compile_error(). It generates the same kind of error, but the little ^^^^ arrow points right at the unknown in [prometheus(unknown)] instead of #[derive(...)]

We could do these for the other sites too, syn::Error::new_spanned(&ast, "...") for the enum/union/unnamed-struct cases points at the offending type.

}
Err(_) => {
return quote! {
compile_error!(
"Attribute on `#[prometheus(...)]` must be an identifier, e.g. `#[prometheus(flatten)]`"
);
}
.into();
}
}
}
}
}

let body: TokenStream2 = match ast.clone().data {
syn::Data::Struct(s) => match s.fields {
syn::Fields::Named(syn::FieldsNamed { named, .. }) => named
Expand All @@ -27,13 +54,7 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {
.iter()
.find(|a| a.path().is_ident("prometheus"))
.map(|a| a.parse_args::<syn::Ident>().unwrap().to_string());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This .unwrap() is only safe because the pre-pass already filtered out unparseable attrs. This is easy to break later, and it means we parse every #[prometheus(...)] twice. Having the closure return a Result and .collect()-ing it would drop the pre-pass, the double parse, and the unwrap all at once.

let flatten = match attribute.as_deref() {
Some("flatten") => true,
Some(other) => {
panic!("Provided attribute '{other}', but only 'flatten' is supported")
}
None => false,
};
let flatten = attribute.as_deref() == Some("flatten");
let ident = f.ident.unwrap();
if flatten {
quote! {
Expand All @@ -60,14 +81,30 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {
})
.collect(),
syn::Fields::Unnamed(_) => {
panic!("Can not derive Encode for struct with unnamed fields.")
return quote! {
compile_error!("Can not derive Encode for struct with unnamed fields.");
}
.into();
}
syn::Fields::Unit => {
return quote! {
compile_error!("Can not derive Encode for struct with unit field.");
}
.into();
}
syn::Fields::Unit => panic!("Can not derive Encode for struct with unit field."),
},
syn::Data::Enum(syn::DataEnum { .. }) => {
panic!("Can not derive Encode for enum.")
return quote! {
compile_error!("Can not derive Encode for enum.");
}
.into();
}
syn::Data::Union(_) => {
return quote! {
compile_error!("Can not derive Encode for union.");
}
.into();
}
syn::Data::Union(_) => panic!("Can not derive Encode for union."),
};

let gen = quote! {
Expand Down Expand Up @@ -95,7 +132,10 @@ pub fn derive_encode_label_value(input: TokenStream) -> TokenStream {

let body = match ast.clone().data {
syn::Data::Struct(_) => {
panic!("Can not derive EncodeLabel for struct.")
return quote! {
compile_error!("Can not derive EncodeLabel for struct.");
}
.into();
}
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let match_arms: TokenStream2 = variants
Expand All @@ -114,7 +154,12 @@ pub fn derive_encode_label_value(input: TokenStream) -> TokenStream {
}
}
}
syn::Data::Union(_) => panic!("Can not derive Encode for union."),
syn::Data::Union(_) => {
return quote! {
compile_error!("Can not derive Encode for union.");
}
.into();
}
};

let gen = quote! {
Expand Down
37 changes: 37 additions & 0 deletions derive-encode/tests/build/friendly-compilation-error-msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use prometheus_client::encoding::EncodeLabelSet;
use prometheus_client::encoding::EncodeLabelValue;

// Unnamed struct fields: not supported by `EncodeLabelSet`.
#[derive(EncodeLabelSet)]
struct Unnamed(String);

// Unit struct: not supported by `EncodeLabelSet`.
#[derive(EncodeLabelSet)]
struct UnitStruct;

// Enum: not supported by `EncodeLabelSet`.
#[derive(EncodeLabelSet)]
enum NotAllowedSet {
A,
B,
}

// Unknown attribute: only `#[prometheus(flatten)]` is accepted.
#[derive(EncodeLabelSet)]
struct BadAttr {
#[prometheus(unknown)]
a: u64,
}

// Struct: not supported by `EncodeLabelValue` (only enums are).
#[derive(EncodeLabelValue)]
struct StructForValue;

// Union: not supported by either derive.
#[derive(EncodeLabelSet)]
union UnionSet {
a: u32,
b: f32,
}

fn main() {}
47 changes: 47 additions & 0 deletions derive-encode/tests/build/friendly-compilation-error-msg.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
error: Can not derive Encode for struct with unnamed fields.
--> tests/build/friendly-compilation-error-msg.rs:5:10
|
5 | #[derive(EncodeLabelSet)]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the derive macro `EncodeLabelSet` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Can not derive Encode for struct with unit field.
--> tests/build/friendly-compilation-error-msg.rs:9:10
|
9 | #[derive(EncodeLabelSet)]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the derive macro `EncodeLabelSet` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Can not derive Encode for enum.
--> tests/build/friendly-compilation-error-msg.rs:13:10
|
13 | #[derive(EncodeLabelSet)]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the derive macro `EncodeLabelSet` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Provided attribute 'unknown', but only 'flatten' is supported
--> tests/build/friendly-compilation-error-msg.rs:20:10
|
20 | #[derive(EncodeLabelSet)]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the derive macro `EncodeLabelSet` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Can not derive EncodeLabel for struct.
--> tests/build/friendly-compilation-error-msg.rs:27:10
|
27 | #[derive(EncodeLabelValue)]
| ^^^^^^^^^^^^^^^^
|
= note: this error originates in the derive macro `EncodeLabelValue` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Can not derive Encode for union.
--> tests/build/friendly-compilation-error-msg.rs:31:10
|
31 | #[derive(EncodeLabelSet)]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the derive macro `EncodeLabelSet` (in Nightly builds, run with -Z macro-backtrace for more info)
3 changes: 2 additions & 1 deletion derive-encode/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,6 @@ fn flatten() {
#[test]
fn build() {
let t = trybuild::TestCases::new();
t.pass("tests/build/redefine-prelude-symbols.rs")
t.pass("tests/build/redefine-prelude-symbols.rs");
t.compile_fail("tests/build/friendly-compilation-error-msg.rs");
}