From 1a4f1116d51629336f74d0ee9ed2a5cd1ef2cccf Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Thu, 2 May 2024 14:24:37 -0700 Subject: [PATCH] feat(forge): Add new jinja filters and tests --- Cargo.lock | 126 ++++---- crates/weaver_forge/src/extensions/code.rs | 92 ++++++ crates/weaver_forge/src/extensions/mod.rs | 2 + crates/weaver_forge/src/extensions/otel.rs | 341 +++++++++++++++++++++ crates/weaver_forge/src/lib.rs | 45 ++- docs/images/dependencies.svg | 112 ++++--- 6 files changed, 597 insertions(+), 121 deletions(-) create mode 100644 crates/weaver_forge/src/extensions/code.rs create mode 100644 crates/weaver_forge/src/extensions/otel.rs diff --git a/Cargo.lock b/Cargo.lock index b05c3ae4..93b55619 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,47 +63,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -165,9 +166,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" @@ -251,9 +252,9 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -354,9 +355,9 @@ checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "const_format" @@ -495,9 +496,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deranged" @@ -601,9 +602,9 @@ checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "filetime" @@ -613,15 +614,15 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "windows-sys 0.52.0", ] [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -1539,9 +1540,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -1757,6 +1758,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "iso8601" version = "0.6.1" @@ -1931,9 +1938,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" @@ -1959,9 +1966,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2054,9 +2061,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minijinja" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5c5e3d2b4c0a6832bd3d571f7c19a7c1c1f05f11a6e85ae1a29f76be5f9455" +checksum = "55e877d961d4f96ce13615862322df7c0b6d169d40cab71a7ef3f9b9e594451e" dependencies = [ "aho-corasick", "memo-map", @@ -2253,9 +2260,9 @@ checksum = "8fecab3723493c7851f292cb060f3ee1c42f19b8d749345d0d7eaf3fd19aa62d" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -2263,22 +2270,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "parse-zoneinfo" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] @@ -2289,7 +2296,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "serde", ] @@ -2516,6 +2523,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -2604,7 +2620,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", @@ -2697,7 +2713,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] @@ -2776,18 +2792,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -2920,9 +2936,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3307,9 +3323,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -3335,7 +3351,7 @@ version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "flate2", "log", "once_cell", @@ -3680,9 +3696,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ "windows-sys 0.52.0", ] @@ -3843,9 +3859,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] diff --git a/crates/weaver_forge/src/extensions/code.rs b/crates/weaver_forge/src/extensions/code.rs new file mode 100644 index 00000000..7ae7189b --- /dev/null +++ b/crates/weaver_forge/src/extensions/code.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Set of filters used to facilitate the generation of code. + +use std::collections::HashMap; + +use minijinja::Value; + +/// Converts the input string into a comment with a prefix. +#[must_use] +pub(crate) fn comment_with_prefix(input: &str, prefix: &str) -> String { + let mut comment = String::new(); + for line in input.lines() { + if !comment.is_empty() { + comment.push('\n'); + } + comment.push_str(&format!("{}{}", prefix, line)); + } + comment +} + +/// Create a filter that uses the type mapping defined in `weaver.yaml` to replace +/// the input value (i.e. OTel type) with the target type. +/// +/// # Returns +/// +/// A function that takes an input value and returns a new string value with the +/// data type replaced. If the input value is not found in the type mapping or is +/// not a string, the input value is returned as is. +pub(crate) fn type_mapping(type_mapping: HashMap) -> impl Fn(&Value) -> Value { + move |input: &Value| -> Value { + if let Some(input_as_str) = input.as_str() { + if let Some(target_type) = type_mapping.get(input_as_str) { + Value::from(target_type.as_str()) + } else { + input.to_owned() + } + } else { + input.to_owned() + } + } +} + +#[cfg(test)] +mod tests { + use crate::extensions::code; + + use super::*; + + #[test] + fn test_comment() { + assert_eq!(comment_with_prefix("test", "// "), "// test"); + + let brief = r#"These attributes may be used to describe the client in a connection-based network interaction +where there is one side that initiates the connection (the client is the side that initiates the connection). +This covers all TCP network interactions since TCP is connection-based and one side initiates the +connection (an exception is made for peer-to-peer communication over TCP where the "user-facing" surface of the +protocol / API doesn't expose a clear notion of client and server). +This also covers UDP network interactions where one side initiates the interaction, e.g. QUIC (HTTP/3) and DNS."#; + + let expected_brief = r#"/// These attributes may be used to describe the client in a connection-based network interaction +/// where there is one side that initiates the connection (the client is the side that initiates the connection). +/// This covers all TCP network interactions since TCP is connection-based and one side initiates the +/// connection (an exception is made for peer-to-peer communication over TCP where the "user-facing" surface of the +/// protocol / API doesn't expose a clear notion of client and server). +/// This also covers UDP network interactions where one side initiates the interaction, e.g. QUIC (HTTP/3) and DNS."#; + + assert_eq!(comment_with_prefix(brief, "/// "), expected_brief); + } + + #[test] + fn test_mapping() { + let type_mapping = vec![ + ("string".to_owned(), "String".to_owned()), + ("int".to_owned(), "i64".to_owned()), + ("double".to_owned(), "f64".to_owned()), + ("boolean".to_owned(), "bool".to_owned()), + ]; + + let filter = code::type_mapping(type_mapping.into_iter().collect()); + + assert_eq!(filter(&Value::from("int")), Value::from("i64")); + assert_eq!(filter(&Value::from("double")), Value::from("f64")); + assert_eq!(filter(&Value::from("string")), Value::from("String")); + assert_eq!(filter(&Value::from("boolean")), Value::from("bool")); + assert_eq!( + filter(&Value::from("something else")), + Value::from("something else") + ); + assert_eq!(filter(&Value::from(12)), Value::from(12)); + } +} diff --git a/crates/weaver_forge/src/extensions/mod.rs b/crates/weaver_forge/src/extensions/mod.rs index dd8bb066..40e53026 100644 --- a/crates/weaver_forge/src/extensions/mod.rs +++ b/crates/weaver_forge/src/extensions/mod.rs @@ -3,3 +3,5 @@ //! Custom filters used by the template engine. pub mod acronym; pub mod case_converter; +pub mod code; +pub mod otel; diff --git a/crates/weaver_forge/src/extensions/otel.rs b/crates/weaver_forge/src/extensions/otel.rs new file mode 100644 index 00000000..61c7f297 --- /dev/null +++ b/crates/weaver_forge/src/extensions/otel.rs @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Set of filters, tests, and functions that are specific to the OpenTelemetry project. + +use crate::config::CaseConvention; +use minijinja::{ErrorKind, Value}; + +/// Converts registry.{namespace}.{other}.{components} to {namespace}. +/// +/// A [`minijinja::Error`] is returned if the input does not start with "registry" or does not have +/// at least two parts. Otherwise, it returns the namespace (second part of the input). +pub(crate) fn attribute_registry_namespace(input: &str) -> Result { + let parts: Vec<&str> = input.split('.').collect(); + if parts.len() < 2 || parts[0] != "registry" { + return Err(minijinja::Error::new( + ErrorKind::InvalidOperation, + format!("This attribute registry id `{}` is invalid", input), + )); + } + Ok(parts[1].to_owned()) +} + +/// Converts registry.{namespace}.{other}.{components} to {Namespace} (title case the namespace). +/// +/// A [`minijinja::Error`] is returned if the input does not start with "registry" or does not have +/// at least two parts. Otherwise, it returns the namespace (second part of the input, title case). +pub(crate) fn attribute_registry_title(input: &str) -> Result { + let parts: Vec<&str> = input.split('.').collect(); + if parts.len() < 2 || parts[0] != "registry" { + return Err(minijinja::Error::new( + ErrorKind::InvalidOperation, + format!("This attribute registry id `{}` is invalid", input), + )); + } + Ok(CaseConvention::TitleCase.convert(parts[1])) +} + +/// attribute_registry_file: Converts registry.{namespace}.{other}.{components} to attributes-registry/{namespace}.md (kebab-case namespace). +/// +/// A [`minijinja::Error`] is returned if the input does not start with "registry" or does not have +/// at least two parts. Otherwise, it returns the file path (kebab-case namespace). +pub(crate) fn attribute_registry_file(input: &str) -> Result { + let parts: Vec<&str> = input.split('.').collect(); + if parts.len() < 2 || parts[0] != "registry" { + return Err(minijinja::Error::new( + ErrorKind::InvalidOperation, + format!("This attribute registry id `{}` is invalid", input), + )); + } + Ok(format!( + "attributes-registry/{}.md", + CaseConvention::KebabCase.convert(parts[1]) + )) +} + +/// Converts metric.{namespace}.{other}.{components} to {namespace}. +/// +/// A [`minijinja::Error`] is returned if the input does not start with "metric" or does not have +/// at least two parts. Otherwise, it returns the namespace (second part of the input). +pub(crate) fn metric_namespace(input: &str) -> Result { + let parts: Vec<&str> = input.split('.').collect(); + if parts.len() < 2 || parts[0] != "metric" { + return Err(minijinja::Error::new( + ErrorKind::InvalidOperation, + format!("This metric id `{}` is invalid", input), + )); + } + Ok(parts[1].to_owned()) +} + +/// Converts {namespace}.{attribute_id} to {namespace}. +/// +/// A [`minijinja::Error`] is returned if the input does not have +/// at least two parts. Otherwise, it returns the namespace (first part of the input). +pub(crate) fn attribute_namespace(input: &str) -> Result { + let parts: Vec<&str> = input.split('.').collect(); + if parts.len() < 2 { + return Err(minijinja::Error::new( + ErrorKind::InvalidOperation, + format!("This attribute name `{}` is invalid", input), + )); + } + Ok(parts[0].to_owned()) +} + +/// Checks if the input value is an object with a field named "stability" that has the value "stable". +/// Otherwise, it returns false. +#[must_use] +pub(crate) fn is_stable(input: Value) -> bool { + let result = input.get_attr("stability"); + + if let Ok(stability) = result { + if let Some(stability) = stability.as_str() { + return stability == "stable"; + } + } + false +} + +/// Checks if the input value is an object with a field named "stability" that has the value +/// "experimental". Otherwise, it returns false. +#[must_use] +pub(crate) fn is_experimental(input: Value) -> bool { + let result = input.get_attr("stability"); + + if let Ok(stability) = result { + if let Some(stability) = stability.as_str() { + return stability == "experimental"; + } + } + false +} + +/// Checks if the input value is an object with a field named "stability" that has the value "deprecated". +/// Otherwise, it returns false. +#[must_use] +pub(crate) fn is_deprecated(input: Value) -> bool { + let result = input.get_attr("deprecated"); + + if let Ok(deprecated) = result { + if let Some(deprecated) = deprecated.as_str() { + return !deprecated.is_empty(); + } + } + false +} + +#[cfg(test)] +mod tests { + use crate::extensions::otel::{ + attribute_registry_file, attribute_registry_namespace, attribute_registry_title, + is_deprecated, is_experimental, is_stable, metric_namespace, + }; + use minijinja::value::StructObject; + use minijinja::Value; + + struct DynAttr { + id: String, + r#type: String, + stability: String, + deprecated: Option, + } + + impl StructObject for DynAttr { + fn get_field(&self, field: &str) -> Option { + match field { + "id" => Some(Value::from(self.id.as_str())), + "type" => Some(Value::from(self.r#type.as_str())), + "stability" => Some(Value::from(self.stability.as_str())), + "deprecated" => self.deprecated.as_ref().map(|s| Value::from(s.as_str())), + _ => None, + } + } + } + + struct DynSomethingElse { + id: String, + r#type: String, + } + + impl StructObject for DynSomethingElse { + fn get_field(&self, field: &str) -> Option { + match field { + "id" => Some(Value::from(self.id.as_str())), + "type" => Some(Value::from(self.r#type.as_str())), + _ => None, + } + } + } + + #[test] + fn test_attribute_registry_namespace() { + // A string that does not start with "registry" + let input = "test"; + assert!(attribute_registry_namespace(input).is_err()); + + // A string that starts with "registry" but does not have at least two parts + let input = "registry"; + assert!(attribute_registry_namespace(input).is_err()); + + // A string that starts with "registry" and has at least two parts + let input = "registry.namespace.other.components"; + assert_eq!(attribute_registry_namespace(input).unwrap(), "namespace"); + + // An empty string + let input = ""; + assert!(attribute_registry_namespace(input).is_err()); + } + + #[test] + fn test_attribute_registry_title() { + // A string that does not start with "registry" + let input = "test"; + assert!(attribute_registry_title(input).is_err()); + + // A string that starts with "registry" but does not have at least two parts + let input = "registry"; + assert!(attribute_registry_title(input).is_err()); + + // A string that starts with "registry" and has at least two parts + let input = "registry.namespace.other.components"; + assert_eq!(attribute_registry_title(input).unwrap(), "Namespace"); + + // An empty string + let input = ""; + assert!(attribute_registry_title(input).is_err()); + } + + #[test] + fn test_attribute_registry_file() { + // A string that does not start with "registry" + let input = "test"; + assert!(attribute_registry_file(input).is_err()); + + // A string that starts with "registry" but does not have at least two parts + let input = "registry"; + assert!(attribute_registry_file(input).is_err()); + + // A string that starts with "registry" and has at least two parts + let input = "registry.namespace.other.components"; + assert_eq!( + attribute_registry_file(input).unwrap(), + "attributes-registry/namespace.md" + ); + + // An empty string + let input = ""; + assert!(attribute_registry_file(input).is_err()); + } + + #[test] + fn test_metric_namespace() { + // A string that does not start with "registry" + let input = "test"; + assert!(metric_namespace(input).is_err()); + + // A string that starts with "registry" but does not have at least two parts + let input = "metric"; + assert!(metric_namespace(input).is_err()); + + // A string that starts with "registry" and has at least two parts + let input = "metric.namespace.other.components"; + assert_eq!(metric_namespace(input).unwrap(), "namespace"); + + // An empty string + let input = ""; + assert!(metric_namespace(input).is_err()); + } + + #[test] + fn test_is_stable() { + // An attribute with stability "stable" + let attr = Value::from_struct_object(DynAttr { + id: "test".to_owned(), + r#type: "test".to_owned(), + stability: "stable".to_owned(), + deprecated: None, + }); + assert!(is_stable(attr)); + + // An attribute with stability "deprecated" + let attr = Value::from_struct_object(DynAttr { + id: "test".to_owned(), + r#type: "test".to_owned(), + stability: "deprecated".to_owned(), + deprecated: None, + }); + assert!(!is_stable(attr)); + + // An object without a stability field + let object = Value::from_struct_object(DynSomethingElse { + id: "test".to_owned(), + r#type: "test".to_owned(), + }); + assert!(!is_stable(object)); + } + + #[test] + fn test_is_experimental() { + // An attribute with stability "experimental" + let attr = Value::from_struct_object(DynAttr { + id: "test".to_owned(), + r#type: "test".to_owned(), + stability: "experimental".to_owned(), + deprecated: None, + }); + assert!(is_experimental(attr)); + + // An attribute with stability "stable" + let attr = Value::from_struct_object(DynAttr { + id: "test".to_owned(), + r#type: "test".to_owned(), + stability: "stable".to_owned(), + deprecated: None, + }); + assert!(!is_experimental(attr)); + + // An object without a stability field + let object = Value::from_struct_object(DynSomethingElse { + id: "test".to_owned(), + r#type: "test".to_owned(), + }); + assert!(!is_experimental(object)); + } + + #[test] + fn test_is_deprecated() { + // An attribute with stability "experimental" and a deprecated field with a value + let attr = Value::from_struct_object(DynAttr { + id: "test".to_owned(), + r#type: "test".to_owned(), + stability: "experimental".to_owned(), + deprecated: Some("This is deprecated".to_owned()), + }); + assert!(is_deprecated(attr)); + + // An attribute with stability "stable" and a deprecated field with a value + let attr = Value::from_struct_object(DynAttr { + id: "test".to_owned(), + r#type: "test".to_owned(), + stability: "stable".to_owned(), + deprecated: Some("This is deprecated".to_owned()), + }); + assert!(is_deprecated(attr)); + + // An object without a deprecated field + let object = Value::from_struct_object(DynSomethingElse { + id: "test".to_owned(), + r#type: "test".to_owned(), + }); + assert!(!is_deprecated(object)); + + let attr = Value::from_struct_object(DynAttr { + id: "test".to_owned(), + r#type: "test".to_owned(), + stability: "stable".to_owned(), + deprecated: None, + }); + assert!(!is_deprecated(attr)); + } +} diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index a92fd18d..31fe4e02 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -27,6 +27,7 @@ use crate::debug::error_summary; use crate::error::Error::InvalidConfigFile; use crate::extensions::acronym::acronym; use crate::extensions::case_converter::case_converter; +use crate::extensions::code; use crate::registry::{TemplateGroup, TemplateRegistry}; mod config; @@ -54,6 +55,14 @@ impl Default for GeneratorConfig { } } +impl GeneratorConfig { + /// Create a new generator configuration with the given root directory. + #[must_use] + pub fn new(root_dir: PathBuf) -> Self { + Self { root_dir } + } +} + /// A template object accessible from the template. #[derive(Debug, Clone)] struct TemplateObject { @@ -360,7 +369,12 @@ impl TemplateEngine { error: e.to_string(), })?; - // Register case conversion filters based on the target configuration + // Register code-oriented filters + env.add_filter("comment_with_prefix", code::comment_with_prefix); + env.add_filter( + "type_mapping", + code::type_mapping(self.target_config.type_mapping.clone()), + ); env.add_filter( "file_name", case_converter(self.target_config.file_name.clone()), @@ -381,6 +395,8 @@ impl TemplateEngine { "field_name", case_converter(self.target_config.field_name.clone()), ); + + // Register case conversion filters env.add_filter("lower_case", case_converter(CaseConvention::LowerCase)); env.add_filter("upper_case", case_converter(CaseConvention::UpperCase)); env.add_filter("title_case", case_converter(CaseConvention::TitleCase)); @@ -402,16 +418,34 @@ impl TemplateEngine { env.add_filter("acronym", acronym(self.target_config.acronyms.clone())); + // Register custom OpenTelemetry filters and tests + env.add_filter("attribute_namespace", extensions::otel::attribute_namespace); + env.add_filter( + "attribute_registry_namespace", + extensions::otel::attribute_registry_namespace, + ); + env.add_filter( + "attribute_registry_title", + extensions::otel::attribute_registry_title, + ); + env.add_filter( + "attribute_registry_file", + extensions::otel::attribute_registry_file, + ); + env.add_filter("metric_namespace", extensions::otel::metric_namespace); + // ToDo Implement more filters: required, not_required, stable, experimental, deprecated + env.add_test("stable", extensions::otel::is_stable); + env.add_test("experimental", extensions::otel::is_experimental); + env.add_test("deprecated", extensions::otel::is_deprecated); + // ToDo Implement more tests: required, not_required + // env.add_filter("unique_attributes", extensions::unique_attributes); // env.add_filter("instrument", extensions::instrument); - // env.add_filter("required", extensions::required); - // env.add_filter("not_required", extensions::not_required); // env.add_filter("value", extensions::value); // env.add_filter("with_value", extensions::with_value); // env.add_filter("without_value", extensions::without_value); // env.add_filter("with_enum", extensions::with_enum); // env.add_filter("without_enum", extensions::without_enum); - // env.add_filter("comment", extensions::comment); // env.add_filter( // "type_mapping", // extensions::TypeMapping { @@ -419,9 +453,6 @@ impl TemplateEngine { // }, // ); - // Register custom testers - // tera.register_tester("required", testers::is_required); - // tera.register_tester("not_required", testers::is_not_required); Ok(env) } diff --git a/docs/images/dependencies.svg b/docs/images/dependencies.svg index 2b9d1c03..36f9c51e 100644 --- a/docs/images/dependencies.svg +++ b/docs/images/dependencies.svg @@ -4,153 +4,147 @@ - - - + + + 0 - -weaver_cache + +weaver_cache 1 - -weaver_checker + +weaver_checker 2 - -weaver_common + +weaver_common 1->2 - - + + 3 - -weaver_diff + +weaver_diff 4 - -weaver_forge + +weaver_forge 8 - -weaver_resolver + +weaver_resolver 4->8 - - + + 5 - -weaver_resolved_schema + +weaver_resolved_schema 6 - -weaver_semconv + +weaver_semconv 5->6 - - + + 7 - -weaver_version + +weaver_version 5->7 - - + + 6->2 - - + + 8->0 - - + + 8->3 - - + + 8->5 - - + + 9 - -weaver_semconv_gen + +weaver_semconv_gen - + -9->8 - - +9->4 + + 10 - -xtask + +xtask 11 - -weaver + +weaver 11->1 - - - - - -11->4 - - + + - + 11->9 - - + +