From 806070e4e8d50b5cd77109525d74e1f60626ff58 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Thu, 6 Jun 2024 11:11:22 -0700 Subject: [PATCH] feat(forge): Add `semconv_const` filter. --- Cargo.lock | 4 +- crates/weaver_forge/README.md | 1 + crates/weaver_forge/src/error.rs | 7 +++ crates/weaver_forge/src/extensions/case.rs | 28 ++++++++- crates/weaver_forge/src/extensions/otel.rs | 68 ++++++++++++++++++++++ 5 files changed, 105 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05408a77..c8c1f813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4147,9 +4147,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c52728401e1dc672a56e81e593e912aa54c78f40246869f78359a2bf24d29d" +checksum = "41ff33f391015ecab21cd092389215eb265ef9496a9a07b6bee7d3529831deda" dependencies = [ "memchr", ] diff --git a/crates/weaver_forge/README.md b/crates/weaver_forge/README.md index 96a62bf5..25180add 100644 --- a/crates/weaver_forge/README.md +++ b/crates/weaver_forge/README.md @@ -146,6 +146,7 @@ The following filters are available: - `kebab_case`: Converts a string to kebab-case. - `screaming_kebab_case`: Converts a string to SCREAMING-KEBAB-CASE. - `capitalize_first`: Capitalizes the first letter of a string. +- `semconv_const(case)`: Follows the semantic convention for constants. Underscores are removed and the string is converted to the case provided. - `acronym`: Replaces acronyms in the input string with the full name defined in the `acronyms` section of the `weaver.yaml` configuration file. - `split_ids`: Splits a string by '.' creating a list of nested ids. - `type_mapping`: Converts a semantic convention type to a target type (see weaver.yaml section `type_mapping`). diff --git a/crates/weaver_forge/src/error.rs b/crates/weaver_forge/src/error.rs index 78706abe..9c8d49d7 100644 --- a/crates/weaver_forge/src/error.rs +++ b/crates/weaver_forge/src/error.rs @@ -145,6 +145,13 @@ pub enum Error { error: String, }, + /// Invalid case convention. + #[error("`{case}` is not a valid case convention. Valid case conventions are: lower_case, upper_case, title_case, snake_case, kebab_case, camel_case, pascal_case, screaming_snake_case, and screaming_kebab_case.")] + InvalidCaseConvention { + /// The invalid case + case: String, + }, + /// A generic container for multiple errors. #[error("Errors:\n{0:#?}")] CompoundError(Vec), diff --git a/crates/weaver_forge/src/extensions/case.rs b/crates/weaver_forge/src/extensions/case.rs index 54e4b868..c2456e82 100644 --- a/crates/weaver_forge/src/extensions/case.rs +++ b/crates/weaver_forge/src/extensions/case.rs @@ -3,6 +3,7 @@ //! Case converter filters used by the template engine. use crate::config::{CaseConvention, TargetConfig}; +use crate::error::Error; use minijinja::Environment; /// Add case converter filters to the environment. @@ -41,7 +42,8 @@ pub(crate) fn add_filters(env: &mut Environment<'_>, target_config: &TargetConfi ); } -/// Converts input string to the specified case convention. +/// Converts a `CaseConvention` to a function that converts a string to the specified case +/// convention. #[must_use] pub fn case_converter(case_convention: CaseConvention) -> fn(&str) -> String { match case_convention { @@ -57,6 +59,30 @@ pub fn case_converter(case_convention: CaseConvention) -> fn(&str) -> String { } } +/// Converts a "string" case convention to a function that converts a string to the specified case +/// convention. +/// +/// # Returns +/// +/// Returns an error if the case convention is not recognized or a function that converts a string +/// to the specified case convention. +pub fn str_to_case_converter(case_convention: &str) -> Result String, Error> { + match case_convention { + "lower_case" => Ok(lower_case), + "upper_case" => Ok(upper_case), + "title_case" => Ok(title_case), + "camel_case" => Ok(camel_case), + "pascal_case" => Ok(pascal_case), + "snake_case" => Ok(snake_case), + "screaming_snake_case" => Ok(screaming_snake_case), + "kebab_case" => Ok(kebab_case), + "screaming_kebab_case" => Ok(screaming_kebab_case), + _ => Err(Error::InvalidCaseConvention { + case: case_convention.to_owned(), + }), + } +} + /// Converts input string to lower case fn lower_case(input: &str) -> String { CaseConvention::LowerCase.convert(input) diff --git a/crates/weaver_forge/src/extensions/otel.rs b/crates/weaver_forge/src/extensions/otel.rs index e5359c7a..1ab1bbd3 100644 --- a/crates/weaver_forge/src/extensions/otel.rs +++ b/crates/weaver_forge/src/extensions/otel.rs @@ -8,6 +8,7 @@ use minijinja::{ErrorKind, Value}; use serde::de::Error; use crate::config::CaseConvention; +use crate::extensions::case::str_to_case_converter; const TEMPLATE_PREFIX: &str = "template["; const TEMPLATE_SUFFIX: &str = "]"; @@ -24,6 +25,7 @@ pub(crate) fn add_tests_and_filters(env: &mut minijinja::Environment<'_>) { env.add_filter("not_required", not_required); env.add_filter("instantiated_type", instantiated_type); env.add_filter("enum_type", enum_type); + env.add_filter("semconv_const", semconv_const); env.add_test("stable", is_stable); env.add_test("experimental", is_experimental); @@ -141,6 +143,17 @@ pub(crate) fn attribute_namespace(input: &str) -> Result Result { + let converter = str_to_case_converter(case) + .map_err(|e| minijinja::Error::new(ErrorKind::InvalidOperation, format!("{}", e)))?; + // Remove all _ and convert to the desired case + let converted_input = converter(&input.replace('_', "")); + Ok(converted_input) +} + /// Compares two attributes by their requirement_level, then name. fn compare_requirement_level( lhs: &Value, @@ -1180,4 +1193,59 @@ mod tests { members, } } + + #[test] + fn test_semconv_const() { + let mut env = Environment::new(); + let ctx = serde_json::Value::Null; + + add_tests_and_filters(&mut env); + + assert_eq!( + env.render_str( + "{{ 'messaging.client_id' | semconv_const('screaming_snake_case') }}", + &ctx, + ) + .unwrap(), + "MESSAGING_CLIENTID" + ); + + assert_eq!( + env.render_str( + "{{ 'messaging.client_id' | semconv_const('pascal_case') }}", + &ctx, + ) + .unwrap(), + "MessagingClientid" + ); + + assert_eq!( + env.render_str( + "{{ 'messaging.client.id' | semconv_const('screaming_snake_case') }}", + &ctx, + ) + .unwrap(), + "MESSAGING_CLIENT_ID" + ); + + assert_eq!( + env.render_str( + "{{ 'messaging.client.id' | semconv_const('pascal_case') }}", + &ctx, + ) + .unwrap(), + "MessagingClientId" + ); + + assert!(env + .render_str( + "{{ 'messaging.client.id' | semconv_const('invalid_case') }}", + &ctx, + ) + .is_err()); + + assert!(env + .render_str("{{ 123 | semconv_const('lower_case') }}", &ctx,) + .is_err()); + } }