From 9cd29850041202c5d4ff02306306c47ca45696f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:06:08 -0400 Subject: [PATCH 1/4] chore(deps): bump ordered-float from 4.3.0 to 4.4.0 (#412) Bumps [ordered-float](https://github.com/reem/rust-ordered-float) from 4.3.0 to 4.4.0. - [Release notes](https://github.com/reem/rust-ordered-float/releases) - [Commits](https://github.com/reem/rust-ordered-float/compare/v4.3.0...v4.4.0) --- updated-dependencies: - dependency-name: ordered-float dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2830069..5bcff10f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2592,9 +2592,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d501f1a72f71d3c063a6bbc8f7271fa73aa09fe5d6283b6571e2ed176a2537" +checksum = "83e7ccb95e240b7c9506a3d544f10d935e142cc90b0a1d56954fb44d89ad6b97" dependencies = [ "num-traits", "rand 0.8.5", diff --git a/Cargo.toml b/Cargo.toml index 9147f9e0..772807ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ url = "2.5.2" ureq = "2.10.0" regex = "1.11.0" rayon = "1.10.0" -ordered-float = { version = "4.3.0", features = ["serde", "schemars"] } +ordered-float = { version = "4.4.0", features = ["serde", "schemars"] } walkdir = "2.5.0" anyhow = "1.0.89" itertools = "0.13.0" From e93b758014bacbc0e5899c13d7dbc3905ac6559d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Qu=C3=A9rel?= Date: Tue, 22 Oct 2024 10:06:24 -0700 Subject: [PATCH 2/4] New `body_fields` filter to simplify the processing of event body fields (#414) * feat(forge): Add body_fields filter * fix(fmt): Fix cargo fmt issue * fix(build): Fix cargo clippy lint issue --- crates/weaver_forge/README.md | 17 ++ crates/weaver_forge/src/extensions/otel.rs | 240 ++++++++++++++++++++- src/cli.rs | 1 + 3 files changed, 250 insertions(+), 8 deletions(-) diff --git a/crates/weaver_forge/README.md b/crates/weaver_forge/README.md index 09a16305..7a69e1f3 100644 --- a/crates/weaver_forge/README.md +++ b/crates/weaver_forge/README.md @@ -551,6 +551,23 @@ The following filters are available: - `print_value`: Filter returning a quoted and escaped string representation of the input if the input is of type string (JSON escape rules are used). Numbers and booleans are stringified without the quotes, and an empty string is returned for other types. +- `body_fields`: A filter that returns a list of triples (`path`, `field`, `depth`) from a + body field in depth-first order. This filter can be used to iterate over a tree of fields + in a body. The parameter `sort_by` can be used to sort the fields by the given key (by + default, the fields at each level are sorted by their IDs). + + Example of usage: + + ```jinja + {% for path, field, depth in body|body_fields %} + Do something with {{ field }} at depth {{ depth }} with path {{ path }} + {% endfor %} + + {% for path, field, depth in body|body_fields(sort_by=`type`) %} + Do something with {{ field }} at depth {{ depth }} with path {{ path }} + {% endfor %} + ``` + > Please open an issue if you have any suggestions for new filters. They are easy to implement. diff --git a/crates/weaver_forge/src/extensions/otel.rs b/crates/weaver_forge/src/extensions/otel.rs index b449ef39..b377339f 100644 --- a/crates/weaver_forge/src/extensions/otel.rs +++ b/crates/weaver_forge/src/extensions/otel.rs @@ -2,15 +2,15 @@ //! Set of filters, tests, and functions that are specific to the OpenTelemetry project. -use itertools::Itertools; -use minijinja::value::ValueKind; -use minijinja::{ErrorKind, Value}; -use serde::de::Error; - use crate::config::CaseConvention; use crate::extensions::case::{ camel_case, kebab_case, pascal_case, screaming_snake_case, snake_case, }; +use itertools::Itertools; +use minijinja::filters::sort; +use minijinja::value::{Kwargs, ValueKind}; +use minijinja::{ErrorKind, State, Value}; +use serde::de::Error; const TEMPLATE_PREFIX: &str = "template["; const TEMPLATE_SUFFIX: &str = "]"; @@ -33,6 +33,7 @@ pub(crate) fn add_filters(env: &mut minijinja::Environment<'_>) { env.add_filter("snake_case_const", snake_case_const); env.add_filter("screaming_snake_case_const", screaming_snake_case_const); env.add_filter("print_member_value", print_member_value); + env.add_filter("body_fields", body_fields); } /// Add OpenTelemetry specific tests to the environment. @@ -453,14 +454,78 @@ pub(crate) fn is_enum(attr: &Value) -> bool { false } +/// Returns a list of pairs {field, depth} from a body field in depth-first order +/// by default. +/// +/// This can be used to iterate over a tree of fields composing an +/// event body. +/// +/// ```jinja +/// {% for path, field, depth in body|body_fields %} +/// Do something with {{ field }} at depth {{ depth }} with path {{ path }} +/// {% endfor %} +/// ``` +pub(crate) fn body_fields( + state: &State<'_, '_>, + body: Value, + kwargs: Kwargs, +) -> Result { + fn traverse_body_fields( + state: &State<'_, '_>, + v: Value, + rv: &mut Vec, + path: String, + depth: i64, + sort_by: &str, + ) -> Result<(), minijinja::Error> { + if v.is_undefined() || v.is_none() { + return Ok(()); + } + + let fields = v + .get_attr("fields") + .map_err(|_| minijinja::Error::custom("Invalid body field"))?; + let id = v + .get_attr("id") + .map_err(|_| minijinja::Error::custom("Invalid body field"))?; + let path = if path.is_empty() { + id.to_string() + } else { + format!("{path}.{id}") + }; + + if fields.is_undefined() { + rv.push(Value::from(vec![Value::from(path), v, Value::from(depth)])); + } else { + rv.push(Value::from(vec![ + Value::from(path.clone()), + v, + Value::from(depth), + ])); + let kwargs = Kwargs::from_iter([("attribute", Value::from(sort_by))]); + for field in sort(state, fields, kwargs)?.try_iter()? { + traverse_body_fields(state, field, rv, path.clone(), depth + 1, sort_by)?; + } + } + + Ok(()) + } + + let mut rv = Vec::new(); + let sort_by = kwargs.get::>("sort_by")?.unwrap_or("id"); + + traverse_body_fields(state, body, &mut rv, "".to_owned(), 0, sort_by)?; + + Ok(Value::from(rv)) +} + #[cfg(test)] mod tests { - use std::fmt::Debug; - use std::sync::Arc; - use minijinja::value::Object; use minijinja::{Environment, Value}; use serde::Serialize; + use std::fmt::Debug; + use std::sync::Arc; use crate::extensions::otel; use crate::extensions::otel::{ @@ -469,6 +534,7 @@ mod tests { print_member_value, }; use weaver_resolved_schema::attribute::Attribute; + use weaver_semconv::any_value::{AnyValueCommonSpec, AnyValueSpec}; use weaver_semconv::attribute::BasicRequirementLevelSpec; use weaver_semconv::attribute::PrimitiveOrArrayTypeSpec; use weaver_semconv::attribute::RequirementLevel; @@ -1413,4 +1479,162 @@ mod tests { on multiple lines with characters like ', , \, and /"#)).unwrap(), "\"This is a test\\n on multiple lines with characters like ', , \\\\, and /\""); } + + #[test] + fn test_body_fields() { + #[derive(Serialize)] + struct Event { + body: Option, + } + + let mut env = Environment::new(); + + otel::add_filters(&mut env); + otel::add_tests(&mut env); + + assert_eq!( + env.render_str("{% for path, field, depth in body|body_fields %}{{field.id}}:{{depth}}{% endfor %}", Event { body: None }) + .unwrap(), + "" + ); + + let body = AnyValueSpec::Undefined { + common: AnyValueCommonSpec { + id: "id_undefined".to_owned(), + brief: "a brief".to_owned(), + note: "a note".to_owned(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + }; + + assert_eq!( + env.render_str("{% for path, field, depth in body|body_fields %}{{field.id}}:{{depth}}{% endfor %}", Event { body: Some(body) }) + .unwrap(), + "id_undefined:0" + ); + + let body = AnyValueSpec::String { + common: AnyValueCommonSpec { + id: "id_string".to_owned(), + brief: "a brief".to_owned(), + note: "a note".to_owned(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + }; + + assert_eq!( + env.render_str("{% for path, field, depth in body|body_fields %}{{field.id}}:{{depth}}{% endfor %}", Event { body: Some(body) }) + .unwrap(), + "id_string:0" + ); + + let body = AnyValueSpec::Int { + common: AnyValueCommonSpec { + id: "id_int".to_owned(), + brief: "a brief".to_owned(), + note: "a note".to_owned(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + }; + + assert_eq!( + env.render_str("{% for path, field, depth in body|body_fields %}{{field.id}}:{{depth}}{% endfor %}", Event { body: Some(body) }) + .unwrap(), + "id_int:0" + ); + + let body = AnyValueSpec::Map { + common: AnyValueCommonSpec { + id: "id_map".to_owned(), + brief: "0".to_owned(), + note: Default::default(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + fields: vec![ + AnyValueSpec::String { + common: AnyValueCommonSpec { + id: "id_string".to_owned(), + brief: "0".to_owned(), + note: Default::default(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + }, + AnyValueSpec::Int { + common: AnyValueCommonSpec { + id: "id_int".to_owned(), + brief: "1".to_owned(), + note: Default::default(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + }, + AnyValueSpec::Ints { + common: AnyValueCommonSpec { + id: "id_ints".to_owned(), + brief: "2".to_owned(), + note: Default::default(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + }, + AnyValueSpec::Maps { + common: AnyValueCommonSpec { + id: "id_maps".to_owned(), + brief: "3".to_owned(), + note: Default::default(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + fields: vec![ + AnyValueSpec::Boolean { + common: AnyValueCommonSpec { + id: "id_boolean".to_owned(), + brief: "0".to_owned(), + note: Default::default(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + }, + AnyValueSpec::Enum { + common: AnyValueCommonSpec { + id: "id_enum".to_owned(), + brief: "1".to_owned(), + note: Default::default(), + stability: None, + examples: None, + requirement_level: Default::default(), + }, + members: vec![], + }, + ], + }, + ], + }; + + assert_eq!( + env.render_str("{% for path, field, depth in body|body_fields %}{{path}}:{{field.type}}:{{depth}}|{% endfor %}", Event { body: Some(body.clone()) }) + .unwrap(), + "id_map:map:0|id_map.id_int:int:1|id_map.id_ints:int[]:1|id_map.id_maps:map[]:1|id_map.id_maps.id_boolean:boolean:2|id_map.id_maps.id_enum:enum:2|id_map.id_string:string:1|" + ); + + assert_eq!( + env.render_str("{% for path, field, depth in body|body_fields(sort_by='brief') %}{{path}}:{{field.type}}:{{depth}}|{% endfor %}", Event { body: Some(body) }) + .unwrap(), + "id_map:map:0|id_map.id_string:string:1|id_map.id_int:int:1|id_map.id_ints:int[]:1|id_map.id_maps:map[]:1|id_map.id_maps.id_boolean:boolean:2|id_map.id_maps.id_enum:enum:2|" + ); + } } diff --git a/src/cli.rs b/src/cli.rs index d6973ccb..8834a961 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -38,6 +38,7 @@ pub struct Cli { /// Supported commands. #[derive(Subcommand)] +#[allow(clippy::large_enum_variant)] pub enum Commands { /// Manage Semantic Convention Registry Registry(RegistryCommand), From a75d186bb93cc6876e40a2ec8fe65b68f8dc4ba5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:11:23 -0700 Subject: [PATCH 3/4] chore(deps): bump serde_json from 1.0.128 to 1.0.132 (#422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.128 to 1.0.132. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/1.0.128...1.0.132) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Laurent Quérel --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bcff10f..7b99b4ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3335,9 +3335,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 772807ad..7b05df99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ rust-version = "1.76" [workspace.dependencies] serde = { version = "1.0.210", features = ["derive"] } serde_yaml = "0.9.32" -serde_json = { version = "1.0.128"} +serde_json = { version = "1.0.132"} thiserror = "1.0.64" url = "2.5.2" ureq = "2.10.0" From cab180318462c885ff8c94f573055a1e3ec24cbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:16:51 -0700 Subject: [PATCH 4/4] chore(deps): bump anyhow from 1.0.89 to 1.0.90 (#420) Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.89 to 1.0.90. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.89...1.0.90) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b99b4ce..fdac844e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" [[package]] name = "arbitrary" diff --git a/Cargo.toml b/Cargo.toml index 7b05df99..3557c7c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ regex = "1.11.0" rayon = "1.10.0" ordered-float = { version = "4.4.0", features = ["serde", "schemars"] } walkdir = "2.5.0" -anyhow = "1.0.89" +anyhow = "1.0.90" itertools = "0.13.0" globset = { version = "0.4.15", features = ["serde1"] } miette = { version = "7.2.0", features = ["fancy", "serde"] }