From ecbe6f6e14db2d164c20dcee07711bfd6fddfb26 Mon Sep 17 00:00:00 2001 From: ADD-SP Date: Sat, 19 Apr 2025 16:14:43 +0800 Subject: [PATCH 1/5] feat(prometheus-client-derive): initial implemenation Signed-off-by: ADD-SP --- CHANGELOG.md | 6 +- Cargo.toml | 12 +- derive-encode/Cargo.toml | 8 +- prometheus-client-derive/Cargo.toml | 23 +++ prometheus-client-derive/src/lib.rs | 54 +++++++ .../src/registrant/attribute.rs | 146 ++++++++++++++++++ .../src/registrant/field.rs | 74 +++++++++ .../src/registrant/mod.rs | 90 +++++++++++ .../tests/build/01-parse.rs | 17 ++ .../build/02-redefine-prelude-symbols.rs | 54 +++++++ .../tests/build/03-rename.rs | 13 ++ .../tests/build/03-rename.stderr | 5 + .../tests/build/04-unit.rs | 13 ++ .../tests/build/04-unit.stderr | 5 + .../tests/build/05-help.rs | 15 ++ .../tests/build/06-attributes.rs | 16 ++ prometheus-client-derive/tests/lib.rs | 62 ++++++++ src/lib.rs | 3 + src/registry.rs | 12 ++ 19 files changed, 622 insertions(+), 6 deletions(-) create mode 100644 prometheus-client-derive/Cargo.toml create mode 100644 prometheus-client-derive/src/lib.rs create mode 100644 prometheus-client-derive/src/registrant/attribute.rs create mode 100644 prometheus-client-derive/src/registrant/field.rs create mode 100644 prometheus-client-derive/src/registrant/mod.rs create mode 100644 prometheus-client-derive/tests/build/01-parse.rs create mode 100644 prometheus-client-derive/tests/build/02-redefine-prelude-symbols.rs create mode 100644 prometheus-client-derive/tests/build/03-rename.rs create mode 100644 prometheus-client-derive/tests/build/03-rename.stderr create mode 100644 prometheus-client-derive/tests/build/04-unit.rs create mode 100644 prometheus-client-derive/tests/build/04-unit.stderr create mode 100644 prometheus-client-derive/tests/build/05-help.rs create mode 100644 prometheus-client-derive/tests/build/06-attributes.rs create mode 100644 prometheus-client-derive/tests/lib.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c798c594..16034067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Family::get_or_create_owned` can access a metric in a labeled family. This method avoids the risk of runtime deadlocks at the expense of creating an owned type. See [PR 244]. - + +- Supported derive macro `Registrant` to register a metric set with a + `Registry`. See [PR 270]. + [PR 244]: https://github.com/prometheus/client_rust/pull/244 [PR 257]: https://github.com/prometheus/client_rust/pull/257 +[PR 270]: https://github.com/prometheus/client_rust/pull/270 ### Changed diff --git a/Cargo.toml b/Cargo.toml index a0f5e2a2..f583d817 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,15 @@ default = [] protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] [workspace] -members = ["derive-encode"] +members = ["derive-encode", "prometheus-client-derive"] + +[workspace.dependencies] +proc-macro2 = "1" +quote = "1" +syn = "2" + +# dev-dependencies +trybuild = "1" [dependencies] dtoa = "1.0" @@ -24,6 +32,7 @@ parking_lot = "0.12" prometheus-client-derive-encode = { version = "0.5.0", path = "derive-encode" } prost = { version = "0.12.0", optional = true } prost-types = { version = "0.12.0", optional = true } +prometheus-client-derive = { version = "0.24.0", path = "./prometheus-client-derive" } [dev-dependencies] async-std = { version = "1", features = ["attributes"] } @@ -40,6 +49,7 @@ tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "signal hyper = { version = "1.3.1", features = ["server", "http1"] } hyper-util = { version = "0.1.3", features = ["tokio"] } http-body-util = "0.1.1" +prometheus-client-derive = { path = "./prometheus-client-derive" } [build-dependencies] prost-build = { version = "0.12.0", optional = true } diff --git a/derive-encode/Cargo.toml b/derive-encode/Cargo.toml index 064cce7d..63d5e8a9 100644 --- a/derive-encode/Cargo.toml +++ b/derive-encode/Cargo.toml @@ -12,13 +12,13 @@ documentation = "https://docs.rs/prometheus-client-derive-text-encode" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proc-macro2 = "1" -quote = "1" -syn = "2" +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { workspace = true } [dev-dependencies] prometheus-client = { path = "../", features = ["protobuf"] } -trybuild = "1" +trybuild = { workspace = true } [lib] proc-macro = true diff --git a/prometheus-client-derive/Cargo.toml b/prometheus-client-derive/Cargo.toml new file mode 100644 index 00000000..f9b48524 --- /dev/null +++ b/prometheus-client-derive/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "prometheus-client-derive" +version = "0.24.0" +authors = ["Max Inden "] +edition = "2021" +description = "Macros to derive auxiliary traits for the prometheus-client library." +license = "Apache-2.0 OR MIT" +keywords = ["derive", "prometheus", "metrics", "instrumentation", "monitoring"] +repository = "https://github.com/prometheus/client_rust" +homepage = "https://github.com/prometheus/client_rust" +documentation = "https://docs.rs/prometheus-client" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { workspace = true } + +[dev-dependencies] +prometheus-client = { path = "../" } +trybuild = { workspace = true } diff --git a/prometheus-client-derive/src/lib.rs b/prometheus-client-derive/src/lib.rs new file mode 100644 index 00000000..78e44114 --- /dev/null +++ b/prometheus-client-derive/src/lib.rs @@ -0,0 +1,54 @@ +#![deny(dead_code)] +#![deny(missing_docs)] +#![deny(unused)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +//! This crate provides a procedural macro to derive +//! auxiliary traits for the +//! [`prometheus_client`](https://docs.rs/prometheus-client/latest/prometheus_client/) +mod registrant; + +use proc_macro::TokenStream as TokenStream1; +use proc_macro2::TokenStream as TokenStream2; +use syn::Error; + +type Result = std::result::Result; + +#[proc_macro_derive(Registrant, attributes(registrant))] +/// Derives the `prometheus_client::registry::Registrant` trait implementation for a struct. +/// ```rust +/// use prometheus_client::metrics::counter::Counter; +/// use prometheus_client::metrics::gauge::Gauge; +/// use prometheus_client::registry::{Registry, Registrant as _}; +/// use prometheus_client_derive::Registrant; +/// +/// #[derive(Registrant)] +/// struct Server { +/// /// Number of HTTP requests received +/// /// from the client +/// requests: Counter, +/// /// Memory usage in bytes +/// /// of the server +/// #[registrant(unit = "bytes")] +/// memory_usage: Gauge, +/// } +/// +/// let mut registry = Registry::default(); +/// let server = Server { +/// requests: Counter::default(), +/// memory_usage: Gauge::default(), +/// }; +/// server.register(&mut registry); +/// ``` +/// +/// There are several field attributes: +/// - `#[registrant(rename = "...")]`: Renames the metric. +/// - `#[registrant(unit = "...")]`: Sets the unit of the metric. +/// - `#[registrant(skip)]`: Skips the field and does not register it. +pub fn registrant_derive(input: TokenStream1) -> TokenStream1 { + match registrant::registrant_impl(input.into()) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} diff --git a/prometheus-client-derive/src/registrant/attribute.rs b/prometheus-client-derive/src/registrant/attribute.rs new file mode 100644 index 00000000..c43d497e --- /dev/null +++ b/prometheus-client-derive/src/registrant/attribute.rs @@ -0,0 +1,146 @@ +use quote::ToTokens; +use syn::spanned::Spanned; +use proc_macro2::Span; + +// do not derive debug since this needs "extra-traits" +// feature for crate `syn`, which slows compile time +// too much, and is not needed as this struct is not +// public. +pub struct Attribute { + pub help: Option, + pub unit: Option, + pub rename: Option, + pub skip: bool, +} + +impl Default for Attribute { + fn default() -> Self { + Attribute { + help: None, + unit: None, + rename: None, + skip: false, + } + } +} + +impl Attribute { + fn with_help(mut self, doc: syn::LitStr) -> Self { + self.help = Some(doc); + self + } + + pub(super) fn merge(self, other: Self) -> syn::Result { + let mut merged = self; + + if let Some(doc) = other.help { + // trim leading and trailing whitespace + // and add a space between the two doc strings + let mut acc = merged + .help + .unwrap_or_else(|| syn::LitStr::new("", doc.span())) + .value().trim() + .to_string(); + acc.push(' '); + acc.push_str(&doc.value().trim()); + merged.help = Some(syn::LitStr::new(&acc, Span::call_site())); + } + if let Some(unit) = other.unit { + if merged.unit.is_some() { + return Err(syn::Error::new_spanned( + merged.unit, + "Duplicate `unit` attribute", + )); + } + + merged.unit = Some(unit); + } + if let Some(rename) = other.rename { + if merged.rename.is_some() { + return Err(syn::Error::new_spanned( + merged.rename, + "Duplicate `rename` attribute", + )); + } + + merged.rename = Some(rename); + } + if other.skip { + merged.skip = merged.skip || other.skip; + } + + Ok(merged) + } +} + +impl syn::parse::Parse for Attribute { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let meta = input.parse::()?; + let span = meta.span(); + + match meta { + syn::Meta::NameValue(meta) if meta.path.is_ident("doc") => { + if let syn::Expr::Lit(lit) = meta.value { + let lit_str = syn::parse2::(lit.lit.to_token_stream())?; + return Ok(Attribute::default().with_help(lit_str)); + } else { + return Err(syn::Error::new_spanned( + meta.value, + "Expected a string literal for doc attribute", + )); + } + } + syn::Meta::List(meta) if meta.path.is_ident("registrant") => { + let mut attr = Attribute::default(); + meta.parse_nested_meta(|meta| { + if meta.path.is_ident("unit") { + let unit = meta.value()?.parse::()?; + + if attr.unit.is_some() { + return Err(syn::Error::new( + meta.path.span(), + "Duplicate `unit` attribute", + )); + } + + // unit should be lowercase + let unit = syn::LitStr::new( + unit.value().as_str().to_ascii_lowercase().as_str(), + unit.span(), + ); + attr.unit = Some(unit); + } else if meta.path.is_ident("rename") { + let rename = meta.value()?.parse::()?; + + if attr.rename.is_some() { + return Err(syn::Error::new( + meta.path.span(), + "Duplicate `rename` attribute", + )); + } + + attr.rename = Some(rename); + } else if meta.path.is_ident("skip") { + if attr.skip { + return Err(syn::Error::new( + meta.path.span(), + "Duplicate `skip` attribute", + )); + } + attr.skip = true; + } else { + panic!("Attributes other than `unit` and `rename` should not reach here"); + } + Ok(()) + })?; + Ok(attr) + } + _ => { + return Err(syn::Error::new( + span, + r#"Unknown attribute, expected `#[doc(...)]` or `#[registrant([=value], ...)]`"#, + )) + } + } + } +} diff --git a/prometheus-client-derive/src/registrant/field.rs b/prometheus-client-derive/src/registrant/field.rs new file mode 100644 index 00000000..0cb2c7e1 --- /dev/null +++ b/prometheus-client-derive/src/registrant/field.rs @@ -0,0 +1,74 @@ +use quote::ToTokens; +use crate::registrant::attribute; +use super::attribute::Attribute; + +// do not derive debug since this needs "extra-traits" +// feature for crate `syn`, which slows compile time +// too much, and is not needed as this struct is not +// public. +pub struct Field { + ident: syn::Ident, + name: syn::LitStr, + attr: Attribute, +} + +impl Field { + pub(super) fn ident(&self) -> &syn::Ident { + &self.ident + } + + pub(super) fn name(&self) -> &syn::LitStr { + match &self.attr.rename { + Some(rename) => rename, + None => &self.name, + } + } + + pub(super) fn help(&self) -> syn::LitStr { + self.attr.help.clone() + .unwrap_or_else(|| { + syn::LitStr::new( + "", + self.ident.span(), + ) + }) + } + + pub(super) fn unit(&self) -> Option<&syn::LitStr> { + self.attr.unit.as_ref() + } + + pub(super) fn skip(&self) -> bool { + self.attr.skip + } +} + +impl TryFrom for Field { + type Error = syn::Error; + + fn try_from(field: syn::Field) -> Result { + let ident = field.ident.clone().expect("Fields::Named should have an identifier"); + let name = syn::LitStr::new( + &ident.to_string(), + ident.span(), + ); + let attr = field + .attrs + .into_iter() + // ignore unknown attributes, which might be defined by another derive macros. + .filter(|attr| attr.path().is_ident("doc") || attr.path().is_ident("registrant") ) + .try_fold(vec![], |mut acc, attr| { + acc.push(syn::parse2::(attr.meta.into_token_stream())?); + Ok::, syn::Error>(acc) + })? + .into_iter() + .try_fold(Attribute::default(), |acc, attr| { + acc.merge(attr) + })?; + Ok(Field{ + ident, + name, + attr, + }) + } +} diff --git a/prometheus-client-derive/src/registrant/mod.rs b/prometheus-client-derive/src/registrant/mod.rs new file mode 100644 index 00000000..4f28fa0a --- /dev/null +++ b/prometheus-client-derive/src/registrant/mod.rs @@ -0,0 +1,90 @@ +mod attribute; +mod field; + +use crate::{Error, Result, TokenStream2}; +use quote::quote; + +pub fn registrant_impl(input: TokenStream2) -> Result { + let ast = syn::parse2::(input)?; + // dbg!(&ast); + let name = ast.ident; + let fields = match ast.data { + syn::Data::Struct(body) => match body.fields { + syn::Fields::Named(fields) => fields, + syn::Fields::Unnamed(fields) => { + return Err(Error::new_spanned( + fields, + "Can not derive Registrant for struct with unnamed fields.", + )); + } + syn::Fields::Unit => { + return Err(Error::new_spanned( + name, + "Can not derive Registrant for unit struct.", + )); + } + }, + syn::Data::Enum(_) => { + return Err(Error::new_spanned( + name, + "Can not derive Registrant for enum.", + )); + } + syn::Data::Union(_) => { + return Err(Error::new_spanned( + name, + "Can not derive Registrant for union.", + )); + } + }; + + let register_calls = fields + .named + .into_iter() + .try_fold(vec![], |mut acc, field| { + acc.push(field::Field::try_from(field)?); + Ok::, syn::Error>(acc) + })? + .into_iter() + .filter_map(|field| { + if field.skip() { + return None; + } + + let ident = field.ident(); + let name = field.name(); + let help = field.help(); + let body = match field.unit() { + Some(unit) => { + quote! { + registry.register_with_unit( + #name, + #help, + ::prometheus_client::registry::Unit::Other(#unit.to_string()), + self.#ident.clone(), + ); + } + } + None => { + quote! { + registry.register( + #name, + #help, + self.#ident.clone(), + ); + } + } + }; + + Some(body) + }); + + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + Ok(quote! { + impl #impl_generics ::prometheus_client::registry::Registrant for #name #ty_generics #where_clause { + fn register(&self, registry: &mut ::prometheus_client::registry::Registry) { + #(#register_calls)* + } + } + }) +} diff --git a/prometheus-client-derive/tests/build/01-parse.rs b/prometheus-client-derive/tests/build/01-parse.rs new file mode 100644 index 00000000..aec8176f --- /dev/null +++ b/prometheus-client-derive/tests/build/01-parse.rs @@ -0,0 +1,17 @@ +use prometheus_client::metrics::counter::Counter; +use prometheus_client::metrics::gauge::Gauge; +use prometheus_client_derive::Registrant; + +#[derive(Registrant)] +struct Server { + /// Number of HTTP requests received + /// from the client + #[registrant(rename = "http_requests")] + requests: Counter, + /// Memory usage in bytes + /// of the server + #[registrant(unit = "bytes")] + memory_usage: Gauge, +} + +fn main() {} diff --git a/prometheus-client-derive/tests/build/02-redefine-prelude-symbols.rs b/prometheus-client-derive/tests/build/02-redefine-prelude-symbols.rs new file mode 100644 index 00000000..3b953fca --- /dev/null +++ b/prometheus-client-derive/tests/build/02-redefine-prelude-symbols.rs @@ -0,0 +1,54 @@ +#![allow(unused_imports)] + +// Empty module has nothing and can be used to redefine symbols. +mod empty {} + +// redefine the prelude `::std` +use empty as std; + +// redefine the dependency `::prometheus_client` +use empty as prometheus_client; + +// redefine the dependency `::prometheus_client_derive` +use empty as prometheus_client_derive; + +// redefine the prelude `::core::result::Result`. +type Result = (); + +enum TResult { + Ok, + Err, +} + +// redefine the prelude `::core::result::Result::Ok/Err`. +use TResult::Ok; +use TResult::Err; + +type Option = (); + +enum TOption { + Some, + None, +} + +// redefine the prelude `::core::option::Option::Some/None`. +use TOption::Some; +use TOption::None; + +#[derive(::prometheus_client_derive::Registrant)] +struct Server { + #[registrant(rename = "requests")] + /// Number of HTTP requests received + /// from the client + reqs: ::prometheus_client::metrics::counter::Counter, + + #[registrant(unit = "bytes")] + /// Memory usage in bytes + /// of the server + mem_usage: ::prometheus_client::metrics::gauge::Gauge, + + #[registrant(skip)] + _phantom: (), +} + +fn main() {} diff --git a/prometheus-client-derive/tests/build/03-rename.rs b/prometheus-client-derive/tests/build/03-rename.rs new file mode 100644 index 00000000..a8794bff --- /dev/null +++ b/prometheus-client-derive/tests/build/03-rename.rs @@ -0,0 +1,13 @@ +use prometheus_client::metrics::counter::Counter; +use prometheus_client_derive::Registrant; + +#[derive(Registrant)] +struct Server { + #[registrant(rename = "http_requests")] + requests: Counter, + + #[registrant(rename = http_requests)] + invalid: Counter, +} + +fn main() {} diff --git a/prometheus-client-derive/tests/build/03-rename.stderr b/prometheus-client-derive/tests/build/03-rename.stderr new file mode 100644 index 00000000..028a5371 --- /dev/null +++ b/prometheus-client-derive/tests/build/03-rename.stderr @@ -0,0 +1,5 @@ +error: expected string literal + --> tests/build/03-rename.rs:9:27 + | +9 | #[registrant(rename = http_requests)] + | ^^^^^^^^^^^^^ diff --git a/prometheus-client-derive/tests/build/04-unit.rs b/prometheus-client-derive/tests/build/04-unit.rs new file mode 100644 index 00000000..358e71e8 --- /dev/null +++ b/prometheus-client-derive/tests/build/04-unit.rs @@ -0,0 +1,13 @@ +use prometheus_client::metrics::gauge::Gauge; +use prometheus_client_derive::Registrant; + +#[derive(Registrant)] +struct Server { + #[registrant(unit = "bytes")] + mem_usage: Gauge, + + #[registrant(unit = bytes)] + invalid: Gauge, +} + +fn main() {} diff --git a/prometheus-client-derive/tests/build/04-unit.stderr b/prometheus-client-derive/tests/build/04-unit.stderr new file mode 100644 index 00000000..60d65c57 --- /dev/null +++ b/prometheus-client-derive/tests/build/04-unit.stderr @@ -0,0 +1,5 @@ +error: expected string literal + --> tests/build/04-unit.rs:9:25 + | +9 | #[registrant(unit = bytes)] + | ^^^^^ diff --git a/prometheus-client-derive/tests/build/05-help.rs b/prometheus-client-derive/tests/build/05-help.rs new file mode 100644 index 00000000..66dd8389 --- /dev/null +++ b/prometheus-client-derive/tests/build/05-help.rs @@ -0,0 +1,15 @@ +use prometheus_client::metrics::counter::Counter; +use prometheus_client::metrics::gauge::Gauge; +use prometheus_client_derive::Registrant; + +#[derive(Registrant)] +struct Server { + /// One line help + requests: Counter, + + /// Muti-line help + /// with a lot of text + mem_usage: Gauge, +} + +fn main() {} diff --git a/prometheus-client-derive/tests/build/06-attributes.rs b/prometheus-client-derive/tests/build/06-attributes.rs new file mode 100644 index 00000000..83a14462 --- /dev/null +++ b/prometheus-client-derive/tests/build/06-attributes.rs @@ -0,0 +1,16 @@ +#![allow(unused_imports)] +use prometheus_client::metrics::counter::Counter; +use prometheus_client::metrics::gauge::Gauge; +use prometheus_client_derive::Registrant; + +#[derive(Registrant)] +struct Server { + #[registrant(rename = "memory_usage", unit = "bytes")] // mutiple attributes in single parenthesis + mem_usage: Gauge, + + #[registrant(rename = "tcp_retransmitted")] + #[registrant(unit = "segments")] + tcp_retrans: Gauge, +} + +fn main() {} diff --git a/prometheus-client-derive/tests/lib.rs b/prometheus-client-derive/tests/lib.rs new file mode 100644 index 00000000..00f7da93 --- /dev/null +++ b/prometheus-client-derive/tests/lib.rs @@ -0,0 +1,62 @@ +use prometheus_client::{ + encoding::text::encode, + metrics::counter::Counter, + metrics::gauge::Gauge, + registry::{Registrant as _, Registry}, +}; +use prometheus_client_derive::Registrant; + +#[test] +fn build() { + let t = trybuild::TestCases::new(); + t.pass("tests/build/01-parse.rs"); + t.pass("tests/build/02-redefine-prelude-symbols.rs"); + t.compile_fail("tests/build/03-rename.rs"); + t.compile_fail("tests/build/04-unit.rs"); + t.pass("tests/build/05-help.rs"); + t.pass("tests/build/06-attributes.rs"); +} + +#[test] +fn sanity() { + #[derive(Registrant)] + struct HttpServer { + /// Number of HTTP requests received + /// from the client + #[registrant(rename = "http_requests")] + requests: Counter, + + /// Memory usage in bytes + /// of the server + #[registrant(unit = "bytes")] + memory_usage: Gauge, + + #[registrant(skip)] + #[allow(dead_code)] + skip: (), + } + + let mut registry = Registry::default(); + let http_server = HttpServer { + requests: Counter::default(), + memory_usage: Gauge::default(), + skip: (), + }; + http_server.register(&mut registry); + + let mut buf = String::new(); + encode(&mut buf, ®istry).unwrap(); + + let expected = [ + "# HELP http_requests Number of HTTP requests received from the client.", + "# TYPE http_requests counter", + "http_requests_total 0", + "# HELP memory_usage_bytes Memory usage in bytes of the server.", + "# TYPE memory_usage_bytes gauge", + "# UNIT memory_usage_bytes bytes", + "memory_usage_bytes 0", + "# EOF\n", + ] + .join("\n"); + assert_eq!(buf, expected); +} diff --git a/src/lib.rs b/src/lib.rs index cfff6238..1335e4cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,3 +83,6 @@ pub mod collector; pub mod encoding; pub mod metrics; pub mod registry; + +#[doc(inline)] +pub use prometheus_client_derive::Registrant; diff --git a/src/registry.rs b/src/registry.rs index c7dec3ba..c1359da9 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -389,3 +389,15 @@ pub trait Metric: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug impl Metric for T where T: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static {} + +/// Something that can be registered +pub trait Registrant { + // An alternative signature would be: + // ``` + // fn register(registry: &mut Registry) -> Self; + // ``` + // But this is not dyn compatible. + + /// Register into the given registry. + fn register(&self, registry: &mut Registry); +} From a194c890f09350dce015b696a5546945d916b8b7 Mon Sep 17 00:00:00 2001 From: ADD-SP Date: Sat, 19 Apr 2025 16:34:04 +0800 Subject: [PATCH 2/5] style(prometheus-client-derive): fix rustfmt and clippy reports Signed-off-by: ADD-SP --- .../src/registrant/attribute.rs | 35 ++++++------------ .../src/registrant/field.rs | 37 +++++++------------ 2 files changed, 26 insertions(+), 46 deletions(-) diff --git a/prometheus-client-derive/src/registrant/attribute.rs b/prometheus-client-derive/src/registrant/attribute.rs index c43d497e..63295b01 100644 --- a/prometheus-client-derive/src/registrant/attribute.rs +++ b/prometheus-client-derive/src/registrant/attribute.rs @@ -1,11 +1,12 @@ +use proc_macro2::Span; use quote::ToTokens; use syn::spanned::Spanned; -use proc_macro2::Span; // do not derive debug since this needs "extra-traits" // feature for crate `syn`, which slows compile time // too much, and is not needed as this struct is not // public. +#[derive(Default)] pub struct Attribute { pub help: Option, pub unit: Option, @@ -13,17 +14,6 @@ pub struct Attribute { pub skip: bool, } -impl Default for Attribute { - fn default() -> Self { - Attribute { - help: None, - unit: None, - rename: None, - skip: false, - } - } -} - impl Attribute { fn with_help(mut self, doc: syn::LitStr) -> Self { self.help = Some(doc); @@ -39,10 +29,11 @@ impl Attribute { let mut acc = merged .help .unwrap_or_else(|| syn::LitStr::new("", doc.span())) - .value().trim() + .value() + .trim() .to_string(); acc.push(' '); - acc.push_str(&doc.value().trim()); + acc.push_str(doc.value().trim()); merged.help = Some(syn::LitStr::new(&acc, Span::call_site())); } if let Some(unit) = other.unit { @@ -82,12 +73,12 @@ impl syn::parse::Parse for Attribute { syn::Meta::NameValue(meta) if meta.path.is_ident("doc") => { if let syn::Expr::Lit(lit) = meta.value { let lit_str = syn::parse2::(lit.lit.to_token_stream())?; - return Ok(Attribute::default().with_help(lit_str)); + Ok(Attribute::default().with_help(lit_str)) } else { - return Err(syn::Error::new_spanned( + Err(syn::Error::new_spanned( meta.value, "Expected a string literal for doc attribute", - )); + )) } } syn::Meta::List(meta) if meta.path.is_ident("registrant") => { @@ -135,12 +126,10 @@ impl syn::parse::Parse for Attribute { })?; Ok(attr) } - _ => { - return Err(syn::Error::new( - span, - r#"Unknown attribute, expected `#[doc(...)]` or `#[registrant([=value], ...)]`"#, - )) - } + _ => Err(syn::Error::new( + span, + r#"Unknown attribute, expected `#[doc(...)]` or `#[registrant([=value], ...)]`"#, + )), } } } diff --git a/prometheus-client-derive/src/registrant/field.rs b/prometheus-client-derive/src/registrant/field.rs index 0cb2c7e1..6ecfcd02 100644 --- a/prometheus-client-derive/src/registrant/field.rs +++ b/prometheus-client-derive/src/registrant/field.rs @@ -1,6 +1,6 @@ -use quote::ToTokens; -use crate::registrant::attribute; use super::attribute::Attribute; +use crate::registrant::attribute; +use quote::ToTokens; // do not derive debug since this needs "extra-traits" // feature for crate `syn`, which slows compile time @@ -25,13 +25,10 @@ impl Field { } pub(super) fn help(&self) -> syn::LitStr { - self.attr.help.clone() - .unwrap_or_else(|| { - syn::LitStr::new( - "", - self.ident.span(), - ) - }) + self.attr + .help + .clone() + .unwrap_or_else(|| syn::LitStr::new("", self.ident.span())) } pub(super) fn unit(&self) -> Option<&syn::LitStr> { @@ -47,28 +44,22 @@ impl TryFrom for Field { type Error = syn::Error; fn try_from(field: syn::Field) -> Result { - let ident = field.ident.clone().expect("Fields::Named should have an identifier"); - let name = syn::LitStr::new( - &ident.to_string(), - ident.span(), - ); + let ident = field + .ident + .clone() + .expect("Fields::Named should have an identifier"); + let name = syn::LitStr::new(&ident.to_string(), ident.span()); let attr = field .attrs .into_iter() // ignore unknown attributes, which might be defined by another derive macros. - .filter(|attr| attr.path().is_ident("doc") || attr.path().is_ident("registrant") ) + .filter(|attr| attr.path().is_ident("doc") || attr.path().is_ident("registrant")) .try_fold(vec![], |mut acc, attr| { acc.push(syn::parse2::(attr.meta.into_token_stream())?); Ok::, syn::Error>(acc) })? .into_iter() - .try_fold(Attribute::default(), |acc, attr| { - acc.merge(attr) - })?; - Ok(Field{ - ident, - name, - attr, - }) + .try_fold(Attribute::default(), |acc, attr| acc.merge(attr))?; + Ok(Field { ident, name, attr }) } } From ce460c07d3b28be4daa4f89dad438fa926e610f2 Mon Sep 17 00:00:00 2001 From: ADD-SP Date: Sat, 19 Apr 2025 16:40:07 +0800 Subject: [PATCH 3/5] chore(Cargo.toml): remove a duplicated dev-dependency Signed-off-by: ADD-SP --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f583d817..2860b2c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "signal hyper = { version = "1.3.1", features = ["server", "http1"] } hyper-util = { version = "0.1.3", features = ["tokio"] } http-body-util = "0.1.1" -prometheus-client-derive = { path = "./prometheus-client-derive" } [build-dependencies] prost-build = { version = "0.12.0", optional = true } From cc680cd898881d08f74d38b8ce395f7cd5ef0150 Mon Sep 17 00:00:00 2001 From: ADD-SP Date: Sat, 19 Apr 2025 19:00:25 +0800 Subject: [PATCH 4/5] style(prometheus-client-derive): fix a typo Signed-off-by: ADD-SP --- prometheus-client-derive/src/registrant/attribute.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prometheus-client-derive/src/registrant/attribute.rs b/prometheus-client-derive/src/registrant/attribute.rs index 63295b01..98a0b32c 100644 --- a/prometheus-client-derive/src/registrant/attribute.rs +++ b/prometheus-client-derive/src/registrant/attribute.rs @@ -23,17 +23,17 @@ impl Attribute { pub(super) fn merge(self, other: Self) -> syn::Result { let mut merged = self; - if let Some(doc) = other.help { + if let Some(help) = other.help { // trim leading and trailing whitespace // and add a space between the two doc strings let mut acc = merged .help - .unwrap_or_else(|| syn::LitStr::new("", doc.span())) + .unwrap_or_else(|| syn::LitStr::new("", help.span())) .value() .trim() .to_string(); acc.push(' '); - acc.push_str(doc.value().trim()); + acc.push_str(help.value().trim()); merged.help = Some(syn::LitStr::new(&acc, Span::call_site())); } if let Some(unit) = other.unit { From fe49848ae2d0d2a861e2685318eeb7a2ca51b253 Mon Sep 17 00:00:00 2001 From: ADD-SP Date: Sat, 19 Apr 2025 19:00:44 +0800 Subject: [PATCH 5/5] style(prometheus-client-derive): remove debugging code Signed-off-by: ADD-SP --- prometheus-client-derive/src/registrant/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/prometheus-client-derive/src/registrant/mod.rs b/prometheus-client-derive/src/registrant/mod.rs index 4f28fa0a..4df46ad2 100644 --- a/prometheus-client-derive/src/registrant/mod.rs +++ b/prometheus-client-derive/src/registrant/mod.rs @@ -6,7 +6,6 @@ use quote::quote; pub fn registrant_impl(input: TokenStream2) -> Result { let ast = syn::parse2::(input)?; - // dbg!(&ast); let name = ast.ident; let fields = match ast.data { syn::Data::Struct(body) => match body.fields {