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
123 changes: 79 additions & 44 deletions derive-encode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::DeriveInput;

fn error_spanned<T: quote::ToTokens>(tokens: &T, msg: &str) -> TokenStream {
syn::Error::new_spanned(tokens, msg)
.to_compile_error()
.into()
}

/// Derive `prometheus_client::encoding::EncodeLabelSet`.
#[proc_macro_derive(EncodeLabelSet, attributes(prometheus))]
pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {
Expand All @@ -19,55 +25,82 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {

let body: TokenStream2 = match ast.clone().data {
syn::Data::Struct(s) => match s.fields {
syn::Fields::Named(syn::FieldsNamed { named, .. }) => named
.into_iter()
.map(|f| {
let attribute = f
.attrs
.iter()
.find(|a| a.path().is_ident("prometheus"))
.map(|a| a.parse_args::<syn::Ident>().unwrap().to_string());
let flatten = match attribute.as_deref() {
Some("flatten") => true,
Some(other) => {
panic!("Provided attribute '{other}', but only 'flatten' is supported")
}
None => false,
};
let ident = f.ident.unwrap();
if flatten {
quote! {
EncodeLabelSet::encode(&self.#ident, encoder)?;
}
} else {
let ident_string = KEYWORD_IDENTIFIERS
syn::Fields::Named(syn::FieldsNamed { named, .. }) => {
let body = named
.into_iter()
.map(|f| {
let ident = f.ident.unwrap();
let flatten = match f
.attrs
.iter()
.find(|pair| ident == pair.1)
.map(|pair| pair.0.to_string())
.unwrap_or_else(|| ident.to_string());

quote! {
let mut label_encoder = encoder.encode_label();
let mut label_key_encoder = label_encoder.encode_label_key()?;
EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?;

let mut label_value_encoder = label_key_encoder.encode_label_value()?;
EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?;

label_value_encoder.finish()?;
.find(|a| a.path().is_ident("prometheus"))
{
None => false,
Some(a) => match a.parse_args::<syn::Ident>() {
Ok(ident) if ident == "flatten" => true,
Ok(other) => {
return Err(error_spanned(
&other,
&format!(
"Provided attribute '{other}', but only 'flatten' is supported"
),
));
}
Err(_) => {
return Err(error_spanned(
a,
"Attribute on `#[prometheus(...)]` must be an identifier, e.g. `#[prometheus(flatten)]`",
));
}
},
};

if flatten {
Ok(quote! {
EncodeLabelSet::encode(&self.#ident, encoder)?;
})
} else {
let ident_string = KEYWORD_IDENTIFIERS
.iter()
.find(|pair| ident == pair.1)
.map(|pair| pair.0.to_string())
.unwrap_or_else(|| ident.to_string());

Ok(quote! {
let mut label_encoder = encoder.encode_label();
let mut label_key_encoder = label_encoder.encode_label_key()?;
EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?;

let mut label_value_encoder = label_key_encoder.encode_label_value()?;
EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?;

label_value_encoder.finish()?;
})
}
}
})
.collect(),
})
.collect::<Result<TokenStream2, TokenStream>>();

match body {
Ok(body) => body,
Err(err) => return err,
}
}
syn::Fields::Unnamed(_) => {
panic!("Can not derive Encode for struct with unnamed fields.")
return error_spanned(
&ast,
"Can not derive Encode for struct with unnamed fields.",
);
}
syn::Fields::Unit => {
return error_spanned(&ast, "Can not derive Encode for struct with unit field.");
}
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 error_spanned(&ast, "Can not derive Encode for enum.");
}
syn::Data::Union(_) => {
return error_spanned(&ast, "Can not derive Encode for union.");
}
syn::Data::Union(_) => panic!("Can not derive Encode for union."),
};

let gen = quote! {
Expand Down Expand Up @@ -95,7 +128,7 @@ 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 error_spanned(&ast, "Can not derive EncodeLabel for struct.");
}
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let match_arms: TokenStream2 = variants
Expand All @@ -114,7 +147,9 @@ pub fn derive_encode_label_value(input: TokenStream) -> TokenStream {
}
}
}
syn::Data::Union(_) => panic!("Can not derive Encode for union."),
syn::Data::Union(_) => {
return error_spanned(&ast, "Can not derive Encode for union.");
}
};

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() {}
41 changes: 41 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,41 @@
error: Can not derive Encode for struct with unnamed fields.
--> tests/build/friendly-compilation-error-msg.rs:6:1
|
6 | struct Unnamed(String);
| ^^^^^^^^^^^^^^^^^^^^^^^

error: Can not derive Encode for struct with unit field.
--> tests/build/friendly-compilation-error-msg.rs:10:1
|
10 | struct UnitStruct;
| ^^^^^^^^^^^^^^^^^^

error: Can not derive Encode for enum.
--> tests/build/friendly-compilation-error-msg.rs:14:1
|
14 | / enum NotAllowedSet {
15 | | A,
16 | | B,
17 | | }
| |_^

error: Provided attribute 'unknown', but only 'flatten' is supported
--> tests/build/friendly-compilation-error-msg.rs:22:18
|
22 | #[prometheus(unknown)]
| ^^^^^^^

error: Can not derive EncodeLabel for struct.
--> tests/build/friendly-compilation-error-msg.rs:28:1
|
28 | struct StructForValue;
| ^^^^^^^^^^^^^^^^^^^^^^

error: Can not derive Encode for union.
--> tests/build/friendly-compilation-error-msg.rs:32:1
|
32 | / union UnionSet {
33 | | a: u32,
34 | | b: f32,
35 | | }
| |_^
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");
}