diff --git a/relay-conventions/build/attributes.rs b/relay-conventions/build/attributes.rs index 9c8e9b91b77..7f6c56333f3 100644 --- a/relay-conventions/build/attributes.rs +++ b/relay-conventions/build/attributes.rs @@ -161,17 +161,7 @@ pub fn format_constant(attr: &Attribute) -> String { } if let Some(deprecation) = deprecation { - write!(&mut out, "#[deprecated").unwrap(); - - if let Some(ref replacement) = deprecation.replacement { - let replacement_name = name_constant(replacement); - write!( - &mut out, - r#"(note="Use [`{replacement_name}`] (`{replacement}`) instead.")"# - ) - .unwrap(); - } - writeln!(&mut out, "]").unwrap(); + write_deprecation_annotation(&mut out, deprecation); } writeln!(&mut out, r#"pub const {name}: &str = "{key}";"#).unwrap(); @@ -179,12 +169,80 @@ pub fn format_constant(attr: &Attribute) -> String { out } +/// Formats an attribute as a function that interpolates a value for +/// the `` placeholder. +pub fn format_interpolating_fn(attribute: &Attribute) -> Option { + let Attribute { + key, + brief: _, + pii: _, + deprecation, + alias: _, + } = attribute; + + let needle = ""; + let placeholder_start = key.find(needle)?; + let placeholder_end = placeholder_start + needle.len(); + let before_placeholder = key.get(..placeholder_start).unwrap_or_default(); + let after_placeholder = key.get(placeholder_end..).unwrap_or_default(); + + let constant_name = name_constant(key); + let fn_name = name_fn(key); + + let mut out = String::new(); + + let example_value = key.replace("", "foobar"); + + writeln!( + &mut out, + r#"/// Instantiates the `` placeholder in the attribute +/// [`{constant_name}`](crate::consts::{constant_name}) (`{key}`) with a concrete value. +/// # Example +/// ``` +/// use relay_conventions::interpolate::{fn_name}; +/// assert_eq!({fn_name}("foobar"), "{example_value}"); +/// ```"# + ) + .unwrap(); + + if let Some(deprecation) = deprecation { + write_deprecation_annotation(&mut out, deprecation); + } + + writeln!( + &mut out, + r#"pub fn {fn_name}(value: &str) -> String {{ + format!("{before_placeholder}{{value}}{after_placeholder}") +}}"# + ) + .unwrap(); + + Some(out) +} + +fn write_deprecation_annotation(out: &mut impl Write, deprecation: &Deprecation) { + write!(out, "#[deprecated").unwrap(); + + if let Some(ref replacement) = deprecation.replacement { + let replacement_name = name_constant(replacement); + write!( + out, + r#"(note="Use [`{replacement_name}`](crate::consts::{replacement_name}) (`{replacement}`) instead.")"# + ) + .unwrap(); + } + writeln!(out, "]").unwrap(); +} + /// Formats an attributes name as a constant identifier. fn name_constant(name: &str) -> String { + name_fn(name).to_ascii_uppercase() +} + +fn name_fn(name: &str) -> String { name.replace(['<', '>'], "") .replace('.', "__") .replace('-', "_") - .to_ascii_uppercase() } /// Parse a path-like attribute key into individual segments. diff --git a/relay-conventions/build/build.rs b/relay-conventions/build/build.rs index c0dfe1af0fd..9d85cd61fc3 100644 --- a/relay-conventions/build/build.rs +++ b/relay-conventions/build/build.rs @@ -27,13 +27,18 @@ fn main() { fn write_attribute_rs(crate_dir: &Path) { use attributes::{ - Attribute, RawNode, constant_pair, format_attribute_info, format_constant, parse_segments, - write_canonical_fn, + Attribute, RawNode, constant_pair, format_attribute_info, format_constant, + format_interpolating_fn, parse_segments, write_canonical_fn, }; let attribute_consts_path = Path::new(&env::var("OUT_DIR").unwrap()).join("attribute_consts.rs"); let mut attribute_consts_file = BufWriter::new(File::create(&attribute_consts_path).unwrap()); + + let interpolation_fns_path = + Path::new(&env::var("OUT_DIR").unwrap()).join("interpolation_fns.rs"); + let mut interpolation_fns_file = BufWriter::new(File::create(&interpolation_fns_path).unwrap()); + let mut attribute_replacement_map = BTreeMap::new(); let mut root = RawNode::default(); @@ -55,6 +60,11 @@ fn write_attribute_rs(crate_dir: &Path) { attribute_replacement_map.insert(old, new); } + // Write interpolating function, if applicable + if let Some(fun) = format_interpolating_fn(&attr) { + writeln!(&mut interpolation_fns_file, "{}\n", fun).unwrap(); + } + // Put attribute info in the hierarchical map let info = format_attribute_info(&attr); diff --git a/relay-conventions/src/lib.rs b/relay-conventions/src/lib.rs index 79fce6134a1..96e5b403c9a 100644 --- a/relay-conventions/src/lib.rs +++ b/relay-conventions/src/lib.rs @@ -54,6 +54,7 @@ //! ### I want to reference an attribute in Relay but it's not defined in `sentry-conventions`, what should I do? //! **Always** define it in `sentry-conventions` before using it in Relay. This makes sure we have proper pub mod consts { + //! Attribute constant definitions. #![allow(rustdoc::bare_urls)] #![allow(non_upper_case_globals)] include!(concat!(env!("OUT_DIR"), "/attribute_consts.rs")); @@ -72,6 +73,12 @@ pub mod consts { pub use self::not_yet_defined::*; } +pub mod interpolate { + //! Functions for interpolating attribute keys with placeholders. + #![allow(non_snake_case)] + include!(concat!(env!("OUT_DIR"), "/interpolation_fns.rs")); +} + include!(concat!(env!("OUT_DIR"), "/attribute_map.rs")); include!(concat!(env!("OUT_DIR"), "/canonical_fn.rs")); include!(concat!(env!("OUT_DIR"), "/name_fn.rs"));