From 1ba992c2cc513635d8b2e87c70eca671f32fca19 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Wed, 8 May 2024 20:10:08 -0700 Subject: [PATCH 01/17] feat(checker): Detect policy files into semconv registry --- Cargo.lock | 2 + Cargo.toml | 1 + crates/weaver_checker/Cargo.toml | 2 + .../data/policies/invalid_policies.rego | 2 +- .../data/policies/invalid_rego_file_1.rego | 1 + .../data/policies/invalid_rego_file_2.rego | 1 + .../policies/invalid_violation_object.rego | 2 +- .../data/policies/otel_policies.rego | 2 +- .../data/registries/otel_policies.rego | 79 +++ crates/weaver_checker/src/lib.rs | 193 +++++- .../semconv_registry/otel_policies.rego | 79 +++ crates/weaver_forge/Cargo.toml | 2 +- crates/weaver_resolver/src/lib.rs | 25 +- data/app-telemetry-schema-1.yaml | 73 --- data/app-telemetry-schema-2.yaml | 161 ----- data/app-telemetry-schema-events.yaml | 57 -- data/app-telemetry-schema-metrics.yaml | 49 -- data/app-telemetry-schema-simple.yaml | 35 -- data/app-telemetry-schema-spans.yaml | 41 -- data/app-telemetry-schema-traces.yaml | 65 -- data/app-telemetry-schema.yaml | 90 --- data/open-telemetry-schema.1.22.0.yaml | 353 ----------- data/prev_registry/otel_policies.rego | 79 +++ data/prev_registry/registry.network.yaml | 20 + data/registry/otel_policies.rego | 79 +++ data/registry/registry.network.yaml | 22 + data/resolved-schema-events.yaml | 573 ----------------- data/resolved-schema-metrics.yaml | 587 ------------------ data/resolved-schema-spans.yaml | 490 --------------- data/resolved_schema.yaml | 561 ----------------- src/registry/check.rs | 19 +- src/registry/generate.rs | 11 +- src/registry/mod.rs | 50 +- src/registry/resolve.rs | 9 +- src/registry/stats.rs | 9 +- 35 files changed, 628 insertions(+), 3196 deletions(-) create mode 100644 crates/weaver_checker/data/policies/invalid_rego_file_1.rego create mode 100644 crates/weaver_checker/data/policies/invalid_rego_file_2.rego create mode 100644 crates/weaver_checker/data/registries/otel_policies.rego create mode 100644 crates/weaver_codegen_test/semconv_registry/otel_policies.rego delete mode 100644 data/app-telemetry-schema-1.yaml delete mode 100644 data/app-telemetry-schema-2.yaml delete mode 100644 data/app-telemetry-schema-events.yaml delete mode 100644 data/app-telemetry-schema-metrics.yaml delete mode 100644 data/app-telemetry-schema-simple.yaml delete mode 100644 data/app-telemetry-schema-spans.yaml delete mode 100644 data/app-telemetry-schema-traces.yaml delete mode 100644 data/app-telemetry-schema.yaml delete mode 100644 data/open-telemetry-schema.1.22.0.yaml create mode 100644 data/prev_registry/otel_policies.rego create mode 100644 data/prev_registry/registry.network.yaml create mode 100644 data/registry/otel_policies.rego create mode 100644 data/registry/registry.network.yaml delete mode 100644 data/resolved-schema-events.yaml delete mode 100644 data/resolved-schema-metrics.yaml delete mode 100644 data/resolved-schema-spans.yaml delete mode 100644 data/resolved_schema.yaml diff --git a/Cargo.lock b/Cargo.lock index 845a8073..8a0bda44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3625,11 +3625,13 @@ dependencies = [ name = "weaver_checker" version = "0.1.0" dependencies = [ + "globset", "regorus", "serde", "serde_json", "serde_yaml", "thiserror", + "walkdir", "weaver_common", ] diff --git a/Cargo.toml b/Cargo.toml index 86401e73..49b78edc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ ordered-float = { version = "4.2.0", features = ["serde"] } walkdir = "2.5.0" anyhow = "1.0.83" itertools = "0.12.1" +globset = { version = "0.4.14", features = ["serde1"] } # Features definition ========================================================= [features] diff --git a/crates/weaver_checker/Cargo.toml b/crates/weaver_checker/Cargo.toml index 29b32c49..03c1ea63 100644 --- a/crates/weaver_checker/Cargo.toml +++ b/crates/weaver_checker/Cargo.toml @@ -20,6 +20,8 @@ thiserror.workspace = true serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true +walkdir.workspace = true +globset.workspace = true regorus = { version = "0.1.4", default-features = false, features = [ "arc", diff --git a/crates/weaver_checker/data/policies/invalid_policies.rego b/crates/weaver_checker/data/policies/invalid_policies.rego index 16393c53..c3bc3717 100644 --- a/crates/weaver_checker/data/policies/invalid_policies.rego +++ b/crates/weaver_checker/data/policies/invalid_policies.rego @@ -1,4 +1,4 @@ -package otel +package before_resolution # Conventions for OTel: # - `data` holds the current released semconv, which is known to be valid. diff --git a/crates/weaver_checker/data/policies/invalid_rego_file_1.rego b/crates/weaver_checker/data/policies/invalid_rego_file_1.rego new file mode 100644 index 00000000..ad2f0b06 --- /dev/null +++ b/crates/weaver_checker/data/policies/invalid_rego_file_1.rego @@ -0,0 +1 @@ +This is not a rego file (use for test purposes only). \ No newline at end of file diff --git a/crates/weaver_checker/data/policies/invalid_rego_file_2.rego b/crates/weaver_checker/data/policies/invalid_rego_file_2.rego new file mode 100644 index 00000000..63fef615 --- /dev/null +++ b/crates/weaver_checker/data/policies/invalid_rego_file_2.rego @@ -0,0 +1 @@ +This is another invalid rego file! (use for test purposes only) \ No newline at end of file diff --git a/crates/weaver_checker/data/policies/invalid_violation_object.rego b/crates/weaver_checker/data/policies/invalid_violation_object.rego index 9613446e..67c5119d 100644 --- a/crates/weaver_checker/data/policies/invalid_violation_object.rego +++ b/crates/weaver_checker/data/policies/invalid_violation_object.rego @@ -1,4 +1,4 @@ -package otel +package before_resolution # Conventions for OTel: # - `data` holds the current released semconv, which is known to be valid. diff --git a/crates/weaver_checker/data/policies/otel_policies.rego b/crates/weaver_checker/data/policies/otel_policies.rego index 66184d3c..ed5a4761 100644 --- a/crates/weaver_checker/data/policies/otel_policies.rego +++ b/crates/weaver_checker/data/policies/otel_policies.rego @@ -1,4 +1,4 @@ -package otel +package before_resolution # Conventions for OTel: # - `data` holds the current released semconv, which is known to be valid. diff --git a/crates/weaver_checker/data/registries/otel_policies.rego b/crates/weaver_checker/data/registries/otel_policies.rego new file mode 100644 index 00000000..ed5a4761 --- /dev/null +++ b/crates/weaver_checker/data/registries/otel_policies.rego @@ -0,0 +1,79 @@ +package before_resolution + +# Conventions for OTel: +# - `data` holds the current released semconv, which is known to be valid. +# - `input` holds the new candidate semconv version, whose validity is unknown. +# +# Note: `data` and `input` are predefined variables in Rego. + +# ========= Violation rules applied on unresolved semconv files ========= + +# A registry `attribute_group` containing at least one `ref` attribute is +# considered invalid. +deny[attr_registry_violation("registry_with_ref_attr", group.id, attr.ref)] { + group := input.groups[_] + startswith(group.id, "registry.") + attr := group.attributes[_] + attr.ref != null +} + +# An attribute whose stability is not `deprecated` but has the deprecated field +# set to true is invalid. +deny[attr_violation("attr_stability_deprecated", group.id, attr.id)] { + group := input.groups[_] + attr := group.attributes[_] + attr.stability != "deprecaded" + attr.deprecated +} + +# An attribute cannot be removed from a group that has already been released. +deny[schema_evolution_violation("attr_removed", old_group.id, old_attr.id)] { + old_group := data.groups[_] + old_attr := old_group.attributes[_] + not attr_exists_in_new_group(old_group.id, old_attr.id) +} + + +# ========= Helper functions ========= + +# Check if an attribute from the old group exists in the new +# group's attributes +attr_exists_in_new_group(group_id, attr_id) { + new_group := input.groups[_] + new_group.id == group_id + attr := new_group.attributes[_] + attr.id == attr_id +} + +# Build an attribute registry violation +attr_registry_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute_registry", + "group": group_id, + "attr": attr_id, + } +} + +# Build an attribute violation +attr_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute", + "group": group_id, + "attr": attr_id, + } +} + +# Build a schema evolution violation +schema_evolution_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "schema_evolution", + "group": group_id, + "attr": attr_id, + } +} \ No newline at end of file diff --git a/crates/weaver_checker/src/lib.rs b/crates/weaver_checker/src/lib.rs index 061f567d..3da573aa 100644 --- a/crates/weaver_checker/src/lib.rs +++ b/crates/weaver_checker/src/lib.rs @@ -2,12 +2,18 @@ #![doc = include_str!("../README.md")] -use crate::violation::Violation; -use crate::Error::CompoundError; +use std::fmt::{Display, Formatter}; +use std::path::Path; + +use globset::Glob; use serde::Serialize; use serde_json::to_value; -use std::path::Path; -use weaver_common::error::{format_errors, WeaverError}; +use walkdir::DirEntry; + +use weaver_common::error::{format_errors, handle_errors, WeaverError}; + +use crate::Error::CompoundError; +use crate::violation::Violation; pub mod violation; @@ -25,6 +31,15 @@ pub enum Error { error: String, }, + /// An invalid policy glob pattern. + #[error("Invalid policy glob pattern '{pattern}', error: {error})")] + InvalidPolicyGlobPattern { + /// The glob pattern that caused the error. + pattern: String, + /// The error that occurred. + error: String, + }, + /// An invalid data. #[error("Invalid data, error: {error})")] InvalidData { @@ -81,6 +96,28 @@ impl WeaverError for Error { } } +/// A list of supported policy packages. +pub enum PolicyPackage { + /// Policies that are evaluated before resolution. + BeforeResolution, + /// Policies that are evaluated after resolution. + AfterResolution, +} + +impl Display for PolicyPackage { + /// Returns the name of the policy package. + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PolicyPackage::BeforeResolution => { + write!(f, "before_resolution") + } + PolicyPackage::AfterResolution => { + write!(f, "after_resolution") + } + } + } +} + /// The policy engine. #[derive(Clone, Default)] pub struct Engine { @@ -112,6 +149,56 @@ impl Engine { }) } + /// Adds all the policy files present in the given directory that match the + /// given glob pattern (Unix-style glob syntax). + /// + /// Example of pattern: `*.rego` + /// + /// # Returns + /// + /// The number of policies added. + pub fn add_policies>(&mut self, policy_dir: P, policy_glob_pattern: &str) -> Result { + let mut errors = Vec::new(); + let mut added_policy_count = 0; + + let policy_glob = Glob::new(policy_glob_pattern).map_err(|e| Error::InvalidPolicyGlobPattern { + pattern: policy_glob_pattern.to_owned(), + error: e.to_string(), + })?.compile_matcher(); + + fn is_hidden(entry: &DirEntry) -> bool { + entry + .file_name() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) + } + let is_policy_file = |entry: &DirEntry| -> bool { + let path = entry.path().to_string_lossy(); + policy_glob.is_match(path.as_ref()) + }; + + // Visit recursively all the files in the policy directory + for entry in walkdir::WalkDir::new(policy_dir) { + if let Ok(entry) = entry { + if is_hidden(&entry) { + continue; + } + if is_policy_file(&entry) { + if let Err(err) = self.add_policy(entry.path()) { + errors.push(err); + } else { + added_policy_count += 1; + } + } + } + } + + handle_errors(errors)?; + + Ok(added_policy_count) + } + /// Adds a data document to the policy engine. /// /// Data versus Input: In essence, data is about what the policy engine @@ -135,6 +222,11 @@ impl Engine { }) } + /// Clears the data from the policy engine. + pub fn clear_data(&mut self) { + self.engine.clear_data(); + } + /// Sets an input document for the policy engine. /// /// Data versus Input: In essence, data is about what the policy engine @@ -158,12 +250,12 @@ impl Engine { Ok(()) } - /// Returns a list of violations based on the policies, the data, and the - /// input. - pub fn check(&mut self) -> Result, Error> { + /// Returns a list of violations based on the policies, the data, the + /// input, and the given package. + pub fn check(&mut self, package: PolicyPackage) -> Result, Error> { let value = self .engine - .eval_rule("data.otel.deny".to_owned()) + .eval_rule(format!("data.{}.deny", package.to_string())) .map_err(|e| Error::ViolationEvaluationError { error: e.to_string(), })?; @@ -185,12 +277,15 @@ impl Engine { #[cfg(test)] mod tests { - use crate::violation::Violation; - use crate::Engine; - use serde_yaml::Value; use std::collections::HashMap; + + use serde_yaml::Value; + use weaver_common::error::format_errors; + use crate::{Engine, Error, PolicyPackage}; + use crate::violation::Violation; + #[test] fn test_policy() -> Result<(), Box> { let mut engine = Engine::new(); @@ -224,16 +319,15 @@ mod tests { attr: "protocol.port".to_owned(), }, ] - .into_iter() - .map(|v| (v.id().to_owned(), v)) - .collect(); + .into_iter() + .map(|v| (v.id().to_owned(), v)) + .collect(); - let violations = engine.check()?; + let violations = engine.check(PolicyPackage::BeforeResolution)?; assert_eq!(violations.len(), 3); for violation in violations { assert_eq!(expected_violations.get(violation.id()), Some(&violation)); - println!("{}", violation); } Ok(()) @@ -265,7 +359,7 @@ mod tests { let new_semconv: Value = serde_yaml::from_str(&new_semconv).unwrap(); engine.set_input(&new_semconv).unwrap(); - let result = engine.check(); + let result = engine.check(PolicyPackage::BeforeResolution); assert!(result.is_err()); let observed_errors = format_errors(&[result.unwrap_err()]); @@ -274,4 +368,69 @@ mod tests { "Violation evaluation error: missing field `type`" ); } + + #[test] + fn test_add_policies() -> Result<(), Box> { + let mut engine = Engine::new(); + let result = engine + .add_policies("data/registries", "*.rego"); + + assert!(result.is_ok()); + + let old_semconv = std::fs::read_to_string("data/registries/registry.network.old.yaml")?; + let old_semconv: Value = serde_yaml::from_str(&old_semconv)?; + engine.add_data(&old_semconv)?; + + let new_semconv = std::fs::read_to_string("data/registries/registry.network.new.yaml")?; + let new_semconv: Value = serde_yaml::from_str(&new_semconv)?; + engine.set_input(&new_semconv)?; + + let expected_violations: HashMap = vec![ + Violation::SemconvAttribute { + id: "attr_stability_deprecated".to_owned(), + category: "attrigute".to_owned(), + group: "registry.network1".to_owned(), + attr: "protocol.name".to_owned(), + }, + Violation::SemconvAttribute { + id: "attr_removed".to_owned(), + category: "schema_evolution".to_owned(), + group: "registry.network1".to_owned(), + attr: "protocol.name.3".to_owned(), + }, + Violation::SemconvAttribute { + id: "registry_with_ref_attr".to_owned(), + category: "attrigute_registry".to_owned(), + group: "registry.network1".to_owned(), + attr: "protocol.port".to_owned(), + }, + ] + .into_iter() + .map(|v| (v.id().to_owned(), v)) + .collect(); + + let violations = engine.check(PolicyPackage::BeforeResolution)?; + assert_eq!(violations.len(), 3); + + for violation in violations { + assert_eq!(expected_violations.get(violation.id()), Some(&violation)); + } + + Ok(()) + } + + #[test] + fn test_add_policies_with_invalid_policies() { + let mut engine = Engine::new(); + let result = engine + .add_policies("data/policies", "*.rego"); + + // We have 2 invalid Rego files in data/policies + assert!(result.is_err()); + if let Error::CompoundError(errors) = result.err().unwrap() { + assert_eq!(errors.len(), 2); + } else { + panic!("Expected a CompoundError"); + } + } } diff --git a/crates/weaver_codegen_test/semconv_registry/otel_policies.rego b/crates/weaver_codegen_test/semconv_registry/otel_policies.rego new file mode 100644 index 00000000..ed5a4761 --- /dev/null +++ b/crates/weaver_codegen_test/semconv_registry/otel_policies.rego @@ -0,0 +1,79 @@ +package before_resolution + +# Conventions for OTel: +# - `data` holds the current released semconv, which is known to be valid. +# - `input` holds the new candidate semconv version, whose validity is unknown. +# +# Note: `data` and `input` are predefined variables in Rego. + +# ========= Violation rules applied on unresolved semconv files ========= + +# A registry `attribute_group` containing at least one `ref` attribute is +# considered invalid. +deny[attr_registry_violation("registry_with_ref_attr", group.id, attr.ref)] { + group := input.groups[_] + startswith(group.id, "registry.") + attr := group.attributes[_] + attr.ref != null +} + +# An attribute whose stability is not `deprecated` but has the deprecated field +# set to true is invalid. +deny[attr_violation("attr_stability_deprecated", group.id, attr.id)] { + group := input.groups[_] + attr := group.attributes[_] + attr.stability != "deprecaded" + attr.deprecated +} + +# An attribute cannot be removed from a group that has already been released. +deny[schema_evolution_violation("attr_removed", old_group.id, old_attr.id)] { + old_group := data.groups[_] + old_attr := old_group.attributes[_] + not attr_exists_in_new_group(old_group.id, old_attr.id) +} + + +# ========= Helper functions ========= + +# Check if an attribute from the old group exists in the new +# group's attributes +attr_exists_in_new_group(group_id, attr_id) { + new_group := input.groups[_] + new_group.id == group_id + attr := new_group.attributes[_] + attr.id == attr_id +} + +# Build an attribute registry violation +attr_registry_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute_registry", + "group": group_id, + "attr": attr_id, + } +} + +# Build an attribute violation +attr_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute", + "group": group_id, + "attr": attr_id, + } +} + +# Build a schema evolution violation +schema_evolution_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "schema_evolution", + "group": group_id, + "attr": attr_id, + } +} \ No newline at end of file diff --git a/crates/weaver_forge/Cargo.toml b/crates/weaver_forge/Cargo.toml index fe9be384..8cbbdd15 100644 --- a/crates/weaver_forge/Cargo.toml +++ b/crates/weaver_forge/Cargo.toml @@ -20,7 +20,6 @@ weaver_semconv = { path = "../weaver_semconv" } minijinja = { version = "2.0.1", features = ["loader", "custom_syntax", "debug", "json", "macros"] } convert_case = "0.6.0" -globset = { version = "0.4.14", features = ["serde1"] } jaq-core = "1.2.1" jaq-std = "1.2.1" jaq-interpret = "1.2.1" @@ -35,6 +34,7 @@ serde_yaml.workspace = true serde_json.workspace = true rayon.workspace = true walkdir.workspace = true +globset.workspace = true [dev-dependencies] opentelemetry = { version = "0.22.0", features = ["trace", "metrics", "logs", "otel_unstable"] } diff --git a/crates/weaver_resolver/src/lib.rs b/crates/weaver_resolver/src/lib.rs index 091cf3f8..af5aee9d 100644 --- a/crates/weaver_resolver/src/lib.rs +++ b/crates/weaver_resolver/src/lib.rs @@ -3,7 +3,7 @@ #![doc = include_str!("../README.md")] use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{MAIN_SEPARATOR, PathBuf}; use rayon::iter::ParallelBridge; use rayon::iter::ParallelIterator; @@ -227,14 +227,24 @@ impl SchemaResolver { registry_path: &RegistryPath, cache: &Cache, ) -> Result, Error> { + let (local_path, registry_path_repr) = Self::path_to_registry(registry_path, cache)?; + Self::load_semconv_from_local_path(local_path, ®istry_path_repr) + } + + /// Returns a tuple absolute ['PathBuf'], logical registry path to the registry based on the + /// given ['RegistryPath'] and the cache. + pub fn path_to_registry( + registry_path: &RegistryPath, + cache: &Cache, + ) -> Result<(PathBuf, String), Error> { match registry_path { RegistryPath::Local { path_pattern: path } => { - Self::load_semconv_from_local_path(path.into(), path) + Ok((path.into(), path.clone())) } RegistryPath::GitUrl { git_url, path } => { match cache.git_repo(git_url.clone(), path.clone()) { Ok(local_git_repo) => { - Self::load_semconv_from_local_path(local_git_repo, git_url) + Ok((local_git_repo, git_url.clone())) } Err(e) => Err(Error::SemConvError { message: e.to_string(), @@ -299,8 +309,13 @@ impl SchemaResolver { .to_str() .map(|s| s.to_owned()) .unwrap_or_default(); - let path = - format!("{}/{}", registry_path_repr, &path[prefix.len() + 1..]); + let path = if registry_path_repr.ends_with(MAIN_SEPARATOR) { + let relative_path = &path[prefix.len() + 0..]; + format!("{}{}", registry_path_repr, relative_path) + } else { + let relative_path = &path[prefix.len() + 1..]; + format!("{}/{}", registry_path_repr, relative_path) + }; Some(Ok((path, spec))) } Err(e) => Some(Err(e)), diff --git a/data/app-telemetry-schema-1.yaml b/data/app-telemetry-schema-1.yaml deleted file mode 100644 index 75b7b09a..00000000 --- a/data/app-telemetry-schema-1.yaml +++ /dev/null @@ -1,73 +0,0 @@ -file_format: 1.2.0 -# Inherit from the OpenTelemetry schema v1.21.0 -parent_schema_url: https://opentelemetry.io/schemas/1.21.0 -# Current schema url -schema_url: https://mycompany.com/schemas/1.0.0 - -# Semantic Convention Imports -semantic_conventions: - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/url.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/http-common.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/client.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/exception.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/server.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/network.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/http.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/database-metrics.yaml - -# The section below outlines the telemetry schema for a service or application. -# This schema details all the metrics, logs, and spans specifically generated -# by that service or application. -# -# Note: Frameworks or libraries linked with the application that produce OTel -# telemetry data might also have their own telemetry schema, detailing the -# metrics, logs, and spans they generate locally. -schema: - # Attributes inherited by all resource types - resource: - attributes: - - ref: service.name - value: "my-service" - - ref: service.version - value: "{{SERVICE_VERSION}}" - - # Metrics declaration - resource_metrics: - # Common attributes shared across univariate and multivariate metrics - attributes: - - id: environment - type: string - brief: The environment in which the service is running - tag: sensitive-information - requirement_level: required - # Declaration of all the univariate metrics - metrics: - - ref: http.server.request.duration - attributes: - - ref: server.address - - ref: server.port - - ref: http.request.method - - ref: http.response.status_code - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - - ref: db.client.connections.usage - attributes: - - ref: server.address - # Declaration of all the multivariate metrics - metric_groups: - - name: http # name of a multivariate metrics group - attributes: - - ref: server.address - - ref: server.port - - ref: http.request.method - - ref: http.response.status_code - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - metrics: # metrics sharing the same attributes - - ref: http.server.request.duration - - ref: http.server.active_requests - - ref: http.server.request.size - - ref: http.server.response.size - - ref: db.client.connections.usage diff --git a/data/app-telemetry-schema-2.yaml b/data/app-telemetry-schema-2.yaml deleted file mode 100644 index 792a3f56..00000000 --- a/data/app-telemetry-schema-2.yaml +++ /dev/null @@ -1,161 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: data/open-telemetry-schema.1.22.0.yaml -# Current schema url -schema_url: https://mycompany.com/schemas/1.0.0 - -# The section below outlines the telemetry schema for a service or application. -# This schema details all the metrics, logs, and spans specifically generated -# by that service or application. -# -# Note: Frameworks or libraries linked with the application that produce OTel -# telemetry data might also have their own telemetry schema, detailing the -# metrics, logs, and spans they generate locally. -schema: - # Resource attributes - # Only used when a Client SDK is generated - resource: - attributes: - - ref: service.name - value: "my-service" - - ref: service.version - requirement_level: required - - id: service.instance.id - type: string - brief: The unique identifier of the service instance - - # Instrumentation library - instrumentation_library: - name: "my-service" - version: "1.0.0" - # schema url? - - # Metrics declaration - resource_metrics: - # Common attributes shared across univariate and multivariate metrics - attributes: - - id: environment - type: string - brief: The environment in which the service is running - tag: sensitive-information - requirement_level: required - # Declaration of all the univariate metrics - metrics: - - ref: jvm.thread.count - attributes: - - ref: server.address - - ref: server.port - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - requirement_level: required - - ref: jvm.class.loaded - attributes: - - ref: server.address - - ref: server.port - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - requirement_level: required - tags: - sensitivity: PII - tags: - sensitivity: PII - - ref: jvm.cpu.recent_utilization - attributes: - - ref: server.address - - ref: server.port - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - requirement_level: required - - ref: jvm.gc.duration - # Declaration of all the multivariate metrics - metric_groups: - - name: http # name of a metrics group - attributes: - - ref: server.address - - ref: server.port - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - - id: url.host - type: string - brief: The host of the request - requirement_level: required - metrics: # metrics sharing the same attributes - - ref: jvm.thread.count - # Note: u64 or f64 must be defined at this level - - ref: jvm.class.loaded - - ref: jvm.cpu.recent_utilization - - # Events declaration - resource_events: - events: - - event_name: request - domain: http - attributes: - - ref: server.address - - ref: server.port - - ref: http.request.method - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - - id: url.host - type: string - brief: The host of the request - requirement_level: required - - event_name: response - domain: http - attributes: - - ref: server.address - - ref: server.port - - ref: http.request.method - - ref: http.response.status_code - - ref: network.protocol.name - - ref: network.protocol.version - - ref: url.scheme - - id: url.host - type: string - brief: The host of the request - requirement_level: required - - # Spans declaration - resource_spans: - spans: - - span_name: http.request # name of a specific tracer - attributes: - - ref: server.address - - ref: server.port - - ref: client.address - - ref: client.port - - ref: url.scheme - - id: url.host - type: string - brief: The host of the request - requirement_level: required - events: - - event_name: error - attributes: - - ref: exception.type - - ref: exception.message - requirement_level: required - - ref: exception.stacktrace - # links: - - span_name: database.query - attributes: - - ref: server.address - - ref: server.port - - ref: client.address - - ref: client.port - - ref: url.scheme - requirement_level: required - - id: url.host - type: string - brief: The host of the request - requirement_level: required - events: - - event_name: error - attributes: - - ref: exception.type - - ref: exception.message - - ref: exception.stacktrace \ No newline at end of file diff --git a/data/app-telemetry-schema-events.yaml b/data/app-telemetry-schema-events.yaml deleted file mode 100644 index 035bfcac..00000000 --- a/data/app-telemetry-schema-events.yaml +++ /dev/null @@ -1,57 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: https://opentelemetry.io/schemas/1.21.0 -schema_url: https://mycompany.com/schemas/1.0.0 -schema: - resource: - attributes: - - ref: service.name - value: "my-service" - - ref: service.version - value: "1.1.1" - - instrumentation_library: - name: "my-service" - version: "1.0.0" - - resource_events: - events: - - event_name: request - domain: http - attributes: - - ref: http.request.method - - ref: network.protocol.name - - ref: network.protocol.version - - ref: http.route - tags: - sensitivity: PII - - ref: server.address - - ref: server.port - - ref: url.scheme - requirement_level: required - - id: mycompany.com.env - type: string - brief: The environment in which the service is running - requirement_level: required - - event_name: response - domain: http - attributes: - - attribute_group_ref: attributes.http.common - - ref: http.response.status_code - requirement_level: required - note: Required status code for HTTP response events. - - ref: http.route - tags: - sensitivity: PII - - ref: server.address - - ref: server.port - - ref: url.scheme - requirement_level: required - - id: mycompany.com.env - type: string - brief: The environment in which the service is running - requirement_level: required - - event_name: error - domain: server - attributes: - - attribute_group_ref: server - - attribute_group_ref: error diff --git a/data/app-telemetry-schema-metrics.yaml b/data/app-telemetry-schema-metrics.yaml deleted file mode 100644 index 546e9331..00000000 --- a/data/app-telemetry-schema-metrics.yaml +++ /dev/null @@ -1,49 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: data/open-telemetry-schema.1.22.0.yaml -schema_url: https://mycompany.com/schemas/1.0.0 - -schema: - resource: - attributes: - - resource_ref: os - - instrumentation_library: - name: "my-service" - version: "1.0.0" - - resource_metrics: - # Common attributes shared across univariate and multivariate metrics - attributes: - - id: environment - type: string - brief: The environment in which the service is running - tag: sensitive-information - requirement_level: required - metrics: - - ref: http.server.request.duration -# attributes: -# - attribute_group_ref: server -# - ref: http.request.method -# - ref: http.response.status_code -# - ref: network.protocol.name -# - ref: network.protocol.version -# - ref: url.scheme - metric_groups: - - name: http -# attributes: -# - attribute_group_ref: server -# - ref: http.request.method -# - ref: http.response.status_code -# - ref: network.protocol.name -# - ref: network.protocol.version -# - ref: url.scheme - metrics: - - ref: http.server.request.duration - - ref: http.server.request.body.size - - ref: http.server.response.body.size -# - id: another_http -# metrics: -# - ref: http.server.request.duration -# - ref: http.server.active_requests -# - ref: http.server.request.body.size -# - ref: http.server.response.body.size diff --git a/data/app-telemetry-schema-simple.yaml b/data/app-telemetry-schema-simple.yaml deleted file mode 100644 index e5cde22f..00000000 --- a/data/app-telemetry-schema-simple.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# This file describes the main sections of the schema file v1.2.0. -# This schema is not intended to be used directly, but rather to be used -# for documentation purposes. - -file_format: 1.2.0 -parent_schema_url: # e.g. https://opentelemetry.io/schemas/1.22.0 -schema_url: -semantic_conventions: # list of semantic conventions to import -schema: - resource: - attributes: # attributes (ref. to semconv or local def.) - instrumentation_library: - name: - version: - resource_metrics: - metrics: # metrics def. (ref. to semconv or local def.) - metric_groups: - - id: - attributes: # attributes (ref. to semconv or local def.) - metrics: # metrics def. (ref. to semconv or local def.) - resource_events: - events: - - event_name: - domain: - attributes: # attributes (ref. to semconv or local def.) - resource_spans: - spans: - - span_name: - attributes: # attributes (ref. to semconv or local def.) - events: # event definitions - links: # link definitions -versions: # version definitions - - - diff --git a/data/app-telemetry-schema-spans.yaml b/data/app-telemetry-schema-spans.yaml deleted file mode 100644 index 7140f1b8..00000000 --- a/data/app-telemetry-schema-spans.yaml +++ /dev/null @@ -1,41 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: https://opentelemetry.io/schemas/1.21.0 -schema_url: https://mycompany.com/schemas/1.0.0 -semantic_conventions: - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/os.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/client.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/exception.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/server.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/feature-flag.yaml - -schema: - resource: - attributes: - - resource_ref: os - - instrumentation_library: - name: "my-service" - version: "1.0.0" - - resource_spans: - spans: - - span_name: client.http.request - attributes: - - attribute_group_ref: client - events: - - event_name: error - attributes: - - span_ref: exception - - event_name: feature_flag - attributes: - - event_ref: feature_flag - - span_name: server.http.request - attributes: - - attribute_group_ref: server - events: - - event_name: error - attributes: - - span_ref: exception - - event_name: feature_flag - attributes: - - event_ref: feature_flag diff --git a/data/app-telemetry-schema-traces.yaml b/data/app-telemetry-schema-traces.yaml deleted file mode 100644 index f645ec62..00000000 --- a/data/app-telemetry-schema-traces.yaml +++ /dev/null @@ -1,65 +0,0 @@ -file_format: 1.2.0 -# Inherit from the OpenTelemetry schema v1.21.0 -parent_schema_url: https://opentelemetry.io/schemas/1.21.0 -# Current schema url -schema_url: https://mycompany.com/schemas/1.0.0 - -# Semantic Convention Imports -semantic_conventions: - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/url.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/http-common.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/client.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/exception.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/server.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/network.yaml - - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/http.yaml - -# The section below outlines the telemetry schema for a service or application. -# This schema details all the metrics, logs, and spans specifically generated -# by that service or application. -# -# Note: Frameworks or libraries linked with the application that produce OTel -# telemetry data might also have their own telemetry schema, detailing the -# metrics, logs, and spans they generate locally. -schema: - # Resource attributes - # Only used when a Client SDK is generated - resource: - attributes: - - ref: service.name - value: "my-service" - - ref: service.version - value: "{{SERVICE_VERSION}}" - # schema url? - - # Instrumentation library - instrumentation_library: - name: "my-service" - version: "1.0.0" - # schema url? - - # Spans declaration - resource_spans: - spans: - - span_name: http.request # name of a specific tracer - attributes: - - ref: server.address - - ref: server.port - - ref: server.socket.address - - ref: server.socket.port - - ref: client.address - - ref: client.port - - ref: client.socket.address - - ref: client.socket.port - - ref: url.scheme - - id: url.host - type: string - brief: The host of the request - requirement_level: required - events: - - event_name: error - attributes: - - ref: exception.type - - ref: exception.message - - ref: exception.stacktrace - # links: \ No newline at end of file diff --git a/data/app-telemetry-schema.yaml b/data/app-telemetry-schema.yaml deleted file mode 100644 index afd53507..00000000 --- a/data/app-telemetry-schema.yaml +++ /dev/null @@ -1,90 +0,0 @@ -file_format: 1.2.0 -# Inherit from the OpenTelemetry schema v1.21.0 -parent_schema_url: ../../data/open-telemetry-schema.1.22.0.yaml -# Current schema url -schema_url: https://mycompany.com/schemas/1.0.0 - -# The section below outlines the telemetry schema for a service or application. -# This schema details all the metrics, logs, and spans specifically generated -# by that service or application. -# -# Note: Frameworks or libraries linked with the application that produce OTel -# telemetry data might also have their own telemetry schema, detailing the -# metrics, logs, and spans they generate locally. -schema: - tags: - sensitive_data: true - - # Resource attributes - # Only used when a Client SDK is generated - resource: - attributes: - - id: service.name - type: string - brief: The name of the service - value: "my-service" - - id: service.version - type: string - brief: The version of the service - value: "{{SERVICE_VERSION}}" - # schema url? - - # Instrumentation library - instrumentation_library: - name: "my-service" - version: "1.0.0" - # schema url? - - # Metrics declaration - resource_metrics: - # Common attributes shared across univariate and multivariate metrics - attributes: - - id: environment - type: string - brief: The environment in which the service is running - tag: sensitive-information - requirement_level: required - # Declaration of all the univariate metrics - metrics: - - ref: http.server.request.duration - attributes: - - ref: server.address - - ref: server.port - - ref: network.protocol.name - - ref: network.protocol.version - # Declaration of all the multivariate metrics - metric_groups: - - name: http # name of a multivariate metrics group - metrics: # metrics sharing the same attributes - - ref: http.server.request.duration - - ref: http.server.request.body.size - - ref: http.server.response.body.size - - - # Events declaration - resource_events: - events: - - event_name: http # name of a specific meter - domain: http - attributes: - - ref: server.address - - ref: server.port - - ref: network.protocol.name - - ref: network.protocol.version - - # Spans declaration - resource_spans: - spans: - - span_name: http.request # name of a specific tracer - attributes: - - ref: server.address - - ref: server.port - - ref: client.address - - ref: client.port - events: - - event_name: error - attributes: - - ref: exception.type - - ref: exception.message - - ref: exception.stacktrace - # links: \ No newline at end of file diff --git a/data/open-telemetry-schema.1.22.0.yaml b/data/open-telemetry-schema.1.22.0.yaml deleted file mode 100644 index 0b6b97e2..00000000 --- a/data/open-telemetry-schema.1.22.0.yaml +++ /dev/null @@ -1,353 +0,0 @@ -file_format: 1.1.0 -schema_url: https://opentelemetry.io/schemas/1.21.0 - -# Semantic conventions can be loaded from a list of URLs or from a git repository. -# The git repository approach is usually faster and download all the semantic -# conventions files dynamically without the need to update the schema file. -semantic_conventions: - - git_url: https://github.com/open-telemetry/semantic-conventions.git - path: model -#semantic_conventions: -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/client.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/destination.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/error.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/exception.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/faas-common.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/general.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/http-common.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/network.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/server.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/session.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/source.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/url.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/user-agent.yaml -# # logs -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/logs/events.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/logs/general.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/logs/log-exception.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/logs/log-feature_flag.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/logs/media.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/logs/mobile-events.yaml -# #metrics -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/database-metrics.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/faas-metrics.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/http.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/jvm-metrics-experimental.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/jvm-metrics.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/rpc-metrics.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/system-metrics.yaml -# # registry -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/registry/http.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/registry/url.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/registry/network.yaml -# # resources -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/android.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/browser.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/cloud.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/container.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/deployment_environment.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/device.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/faas.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/host.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/k8s.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/oci.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/os.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/process.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/service.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/service_experimental.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/telemetry.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/telemetry_experimental.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/webengine.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/cloud_provider/aws/ecs.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/cloud_provider/aws/eks.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/cloud_provider/aws/logs.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/cloud_provider/gcp/cloud_run.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/cloud_provider/gcp/gce.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/resource/cloud_provider/heroku.yaml -# # scope -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/scope/exporter/exporter.yaml -# # trace -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/cloudevents.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/compatibility.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/database.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/faas.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/feature-flag.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/http.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/messaging.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/rpc.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/trace-exception.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/aws/lambda.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/exporter/exporter.yaml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/instrumentation/aws-sdk.yml -# - url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/trace/instrumentation/graphql.yml - -versions: - 1.22.0: - metrics: - changes: - # https://github.com/open-telemetry/semantic-conventions/pull/229 - - rename_attributes: - attribute_map: - messaging.message.payload_size_bytes: messaging.message.body.size - # https://github.com/open-telemetry/semantic-conventions/pull/224 - - rename_metrics: - http.client.duration: http.client.request.duration - http.server.duration: http.server.request.duration - # https://github.com/open-telemetry/semantic-conventions/pull/241 - - rename_metrics: - process.runtime.jvm.memory.usage: jvm.memory.usage - process.runtime.jvm.memory.committed: jvm.memory.committed - process.runtime.jvm.memory.limit: jvm.memory.limit - process.runtime.jvm.memory.usage_after_last_gc: jvm.memory.usage_after_last_gc - process.runtime.jvm.gc.duration: jvm.gc.duration - # also https://github.com/open-telemetry/semantic-conventions/pull/252 - process.runtime.jvm.threads.count: jvm.thread.count - # also https://github.com/open-telemetry/semantic-conventions/pull/252 - process.runtime.jvm.classes.loaded: jvm.class.loaded - # also https://github.com/open-telemetry/semantic-conventions/pull/252 - process.runtime.jvm.classes.unloaded: jvm.class.unloaded - # also https://github.com/open-telemetry/semantic-conventions/pull/252 - # and https://github.com/open-telemetry/semantic-conventions/pull/60 - process.runtime.jvm.classes.current_loaded: jvm.class.count - process.runtime.jvm.cpu.time: jvm.cpu.time - process.runtime.jvm.cpu.recent_utilization: jvm.cpu.recent_utilization - process.runtime.jvm.memory.init: jvm.memory.init - process.runtime.jvm.system.cpu.utilization: jvm.system.cpu.utilization - process.runtime.jvm.system.cpu.load_1m: jvm.system.cpu.load_1m - # https://github.com/open-telemetry/semantic-conventions/pull/253 - process.runtime.jvm.buffer.usage: jvm.buffer.memory.usage - # https://github.com/open-telemetry/semantic-conventions/pull/253 - process.runtime.jvm.buffer.limit: jvm.buffer.memory.limit - process.runtime.jvm.buffer.count: jvm.buffer.count - # https://github.com/open-telemetry/semantic-conventions/pull/20 - - rename_attributes: - attribute_map: - type: jvm.memory.type - pool: jvm.memory.pool.name - apply_to_metrics: - - jvm.memory.usage - - jvm.memory.committed - - jvm.memory.limit - - jvm.memory.usage_after_last_gc - - jvm.memory.init - - rename_attributes: - attribute_map: - name: jvm.gc.name - action: jvm.gc.action - apply_to_metrics: - - jvm.gc.duration - - rename_attributes: - attribute_map: - daemon: thread.daemon - apply_to_metrics: - - jvm.threads.count - - rename_attributes: - attribute_map: - pool: jvm.buffer.pool.name - apply_to_metrics: - - jvm.buffer.usage - - jvm.buffer.limit - - jvm.buffer.count - # https://github.com/open-telemetry/semantic-conventions/pull/89 - - rename_attributes: - attribute_map: - state: system.cpu.state - cpu: system.cpu.logical_number - apply_to_metrics: - - system.cpu.time - - system.cpu.utilization - - rename_attributes: - attribute_map: - state: system.memory.state - apply_to_metrics: - - system.memory.usage - - system.memory.utilization - - rename_attributes: - attribute_map: - state: system.paging.state - apply_to_metrics: - - system.paging.usage - - system.paging.utilization - - rename_attributes: - attribute_map: - type: system.paging.type - direction: system.paging.direction - apply_to_metrics: - - system.paging.faults - - system.paging.operations - - rename_attributes: - attribute_map: - device: system.device - direction: system.disk.direction - apply_to_metrics: - - system.disk.io - - system.disk.operations - - system.disk.io_time - - system.disk.operation_time - - system.disk.merged - - rename_attributes: - attribute_map: - device: system.device - state: system.filesystem.state - type: system.filesystem.type - mode: system.filesystem.mode - mountpoint: system.filesystem.mountpoint - apply_to_metrics: - - system.filesystem.usage - - system.filesystem.utilization - - rename_attributes: - attribute_map: - device: system.device - direction: system.network.direction - protocol: network.protocol - state: system.network.state - apply_to_metrics: - - system.network.dropped - - system.network.packets - - system.network.errors - - system.network.io - - system.network.connections - - rename_attributes: - attribute_map: - status: system.processes.status - apply_to_metrics: - - system.processes.count - # https://github.com/open-telemetry/semantic-conventions/pull/247 - - rename_metrics: - http.server.request.size: http.server.request.body.size - http.server.response.size: http.server.response.body.size - 1.21.0: - spans: - changes: - # https://github.com/open-telemetry/opentelemetry-specification/pull/3336 - - rename_attributes: - attribute_map: - messaging.kafka.client_id: messaging.client_id - messaging.rocketmq.client_id: messaging.client_id - # https://github.com/open-telemetry/opentelemetry-specification/pull/3402 - - rename_attributes: - attribute_map: - # net.peer.(name|port) attributes were usually populated on client side - # so they should be usually translated to server.(address|port) - # net.host.* attributes were only populated on server side - net.host.name: server.address - net.host.port: server.port - # was only populated on client side - net.sock.peer.name: server.socket.domain - # net.sock.peer.(addr|port) mapping is not possible - # since they applied to both client and server side - # were only populated on server side - net.sock.host.addr: server.socket.address - net.sock.host.port: server.socket.port - http.client_ip: client.address - # https://github.com/open-telemetry/opentelemetry-specification/pull/3426 - - rename_attributes: - attribute_map: - net.protocol.name: network.protocol.name - net.protocol.version: network.protocol.version - net.host.connection.type: network.connection.type - net.host.connection.subtype: network.connection.subtype - net.host.carrier.name: network.carrier.name - net.host.carrier.mcc: network.carrier.mcc - net.host.carrier.mnc: network.carrier.mnc - net.host.carrier.icc: network.carrier.icc - # https://github.com/open-telemetry/opentelemetry-specification/pull/3355 - - rename_attributes: - attribute_map: - http.method: http.request.method - http.status_code: http.response.status_code - http.scheme: url.scheme - http.url: url.full - http.request_content_length: http.request.body.size - http.response_content_length: http.response.body.size - metrics: - changes: - # https://github.com/open-telemetry/semantic-conventions/pull/53 - - rename_metrics: - process.runtime.jvm.cpu.utilization: process.runtime.jvm.cpu.recent_utilization - 1.20.0: - spans: - changes: - # https://github.com/open-telemetry/opentelemetry-specification/pull/3272 - - rename_attributes: - attribute_map: - net.app.protocol.name: net.protocol.name - net.app.protocol.version: net.protocol.version - 1.19.0: - spans: - changes: - # https://github.com/open-telemetry/opentelemetry-specification/pull/3209 - - rename_attributes: - attribute_map: - faas.execution: faas.invocation_id - # https://github.com/open-telemetry/opentelemetry-specification/pull/3188 - - rename_attributes: - attribute_map: - faas.id: cloud.resource_id - # https://github.com/open-telemetry/opentelemetry-specification/pull/3190 - - rename_attributes: - attribute_map: - http.user_agent: user_agent.original - resources: - changes: - # https://github.com/open-telemetry/opentelemetry-specification/pull/3190 - - rename_attributes: - attribute_map: - browser.user_agent: user_agent.original - 1.18.0: - 1.17.0: - spans: - changes: - # https://github.com/open-telemetry/opentelemetry-specification/pull/2957 - - rename_attributes: - attribute_map: - messaging.consumer_id: messaging.consumer.id - messaging.protocol: net.app.protocol.name - messaging.protocol_version: net.app.protocol.version - messaging.destination: messaging.destination.name - messaging.temp_destination: messaging.destination.temporary - messaging.destination_kind: messaging.destination.kind - messaging.message_id: messaging.message.id - messaging.conversation_id: messaging.message.conversation_id - messaging.message_payload_size_bytes: messaging.message.payload_size_bytes - messaging.message_payload_compressed_size_bytes: messaging.message.payload_compressed_size_bytes - messaging.rabbitmq.routing_key: messaging.rabbitmq.destination.routing_key - messaging.kafka.message_key: messaging.kafka.message.key - messaging.kafka.partition: messaging.kafka.destination.partition - messaging.kafka.tombstone: messaging.kafka.message.tombstone - messaging.rocketmq.message_type: messaging.rocketmq.message.type - messaging.rocketmq.message_tag: messaging.rocketmq.message.tag - messaging.rocketmq.message_keys: messaging.rocketmq.message.keys - messaging.kafka.consumer_group: messaging.kafka.consumer.group - 1.16.0: - 1.15.0: - spans: - changes: - # https://github.com/open-telemetry/opentelemetry-specification/pull/2743 - - rename_attributes: - attribute_map: - http.retry_count: http.resend_count - 1.14.0: - 1.13.0: - spans: - changes: - # https://github.com/open-telemetry/opentelemetry-specification/pull/2614 - - rename_attributes: - attribute_map: - net.peer.ip: net.sock.peer.addr - net.host.ip: net.sock.host.addr - 1.12.0: - 1.11.0: - 1.10.0: - 1.9.0: - 1.8.0: - spans: - changes: - - rename_attributes: - attribute_map: - db.cassandra.keyspace: db.name - db.hbase.namespace: db.name - 1.7.0: - 1.6.1: - 1.5.0: - 1.4.0: \ No newline at end of file diff --git a/data/prev_registry/otel_policies.rego b/data/prev_registry/otel_policies.rego new file mode 100644 index 00000000..ed5a4761 --- /dev/null +++ b/data/prev_registry/otel_policies.rego @@ -0,0 +1,79 @@ +package before_resolution + +# Conventions for OTel: +# - `data` holds the current released semconv, which is known to be valid. +# - `input` holds the new candidate semconv version, whose validity is unknown. +# +# Note: `data` and `input` are predefined variables in Rego. + +# ========= Violation rules applied on unresolved semconv files ========= + +# A registry `attribute_group` containing at least one `ref` attribute is +# considered invalid. +deny[attr_registry_violation("registry_with_ref_attr", group.id, attr.ref)] { + group := input.groups[_] + startswith(group.id, "registry.") + attr := group.attributes[_] + attr.ref != null +} + +# An attribute whose stability is not `deprecated` but has the deprecated field +# set to true is invalid. +deny[attr_violation("attr_stability_deprecated", group.id, attr.id)] { + group := input.groups[_] + attr := group.attributes[_] + attr.stability != "deprecaded" + attr.deprecated +} + +# An attribute cannot be removed from a group that has already been released. +deny[schema_evolution_violation("attr_removed", old_group.id, old_attr.id)] { + old_group := data.groups[_] + old_attr := old_group.attributes[_] + not attr_exists_in_new_group(old_group.id, old_attr.id) +} + + +# ========= Helper functions ========= + +# Check if an attribute from the old group exists in the new +# group's attributes +attr_exists_in_new_group(group_id, attr_id) { + new_group := input.groups[_] + new_group.id == group_id + attr := new_group.attributes[_] + attr.id == attr_id +} + +# Build an attribute registry violation +attr_registry_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute_registry", + "group": group_id, + "attr": attr_id, + } +} + +# Build an attribute violation +attr_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute", + "group": group_id, + "attr": attr_id, + } +} + +# Build a schema evolution violation +schema_evolution_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "schema_evolution", + "group": group_id, + "attr": attr_id, + } +} \ No newline at end of file diff --git a/data/prev_registry/registry.network.yaml b/data/prev_registry/registry.network.yaml new file mode 100644 index 00000000..0fc51694 --- /dev/null +++ b/data/prev_registry/registry.network.yaml @@ -0,0 +1,20 @@ +groups: + - id: registry.network1 + prefix: network + type: attribute_group + brief: > + These attributes may be used for any network related operation. + attributes: + - id: protocol.name + stability: stable + type: string + brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' + note: The value SHOULD be normalized to lowercase. + examples: ['amqp', 'http', 'mqtt'] + deprecated: true + - id: protocol.name.3 + stability: stable + type: string + brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' + note: The value SHOULD be normalized to lowercase. + examples: ['amqp', 'http', 'mqtt'] \ No newline at end of file diff --git a/data/registry/otel_policies.rego b/data/registry/otel_policies.rego new file mode 100644 index 00000000..ed5a4761 --- /dev/null +++ b/data/registry/otel_policies.rego @@ -0,0 +1,79 @@ +package before_resolution + +# Conventions for OTel: +# - `data` holds the current released semconv, which is known to be valid. +# - `input` holds the new candidate semconv version, whose validity is unknown. +# +# Note: `data` and `input` are predefined variables in Rego. + +# ========= Violation rules applied on unresolved semconv files ========= + +# A registry `attribute_group` containing at least one `ref` attribute is +# considered invalid. +deny[attr_registry_violation("registry_with_ref_attr", group.id, attr.ref)] { + group := input.groups[_] + startswith(group.id, "registry.") + attr := group.attributes[_] + attr.ref != null +} + +# An attribute whose stability is not `deprecated` but has the deprecated field +# set to true is invalid. +deny[attr_violation("attr_stability_deprecated", group.id, attr.id)] { + group := input.groups[_] + attr := group.attributes[_] + attr.stability != "deprecaded" + attr.deprecated +} + +# An attribute cannot be removed from a group that has already been released. +deny[schema_evolution_violation("attr_removed", old_group.id, old_attr.id)] { + old_group := data.groups[_] + old_attr := old_group.attributes[_] + not attr_exists_in_new_group(old_group.id, old_attr.id) +} + + +# ========= Helper functions ========= + +# Check if an attribute from the old group exists in the new +# group's attributes +attr_exists_in_new_group(group_id, attr_id) { + new_group := input.groups[_] + new_group.id == group_id + attr := new_group.attributes[_] + attr.id == attr_id +} + +# Build an attribute registry violation +attr_registry_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute_registry", + "group": group_id, + "attr": attr_id, + } +} + +# Build an attribute violation +attr_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "attrigute", + "group": group_id, + "attr": attr_id, + } +} + +# Build a schema evolution violation +schema_evolution_violation(violation_id, group_id, attr_id) = violation { + violation := { + "id": violation_id, + "type": "semconv_attribute", + "category": "schema_evolution", + "group": group_id, + "attr": attr_id, + } +} \ No newline at end of file diff --git a/data/registry/registry.network.yaml b/data/registry/registry.network.yaml new file mode 100644 index 00000000..5b068366 --- /dev/null +++ b/data/registry/registry.network.yaml @@ -0,0 +1,22 @@ +groups: + - id: registry.network1 + prefix: network + type: attribute_group + brief: > + These attributes may be used for any network related operation. + attributes: + - id: protocol.name + stability: stable + type: string + brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' + note: The value SHOULD be normalized to lowercase. + examples: ['amqp', 'http', 'mqtt'] + deprecated: true + - id: protocol.name.2 + stability: stable + type: string + brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' + note: The value SHOULD be normalized to lowercase. + examples: ['amqp', 'http', 'mqtt'] + - ref: protocol.port + deprecated: true \ No newline at end of file diff --git a/data/resolved-schema-events.yaml b/data/resolved-schema-events.yaml deleted file mode 100644 index 753bee7e..00000000 --- a/data/resolved-schema-events.yaml +++ /dev/null @@ -1,573 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: https://opentelemetry.io/schemas/1.21.0 -schema_url: https://mycompany.com/schemas/1.0.0 -schema: - resource: - attributes: - - ref: service.name - value: my-service - - ref: service.version - value: 1.1.1 - instrumentation_library: - name: my-service - version: 1.0.0 - resource_events: - events: - - event_name: request - domain: http - attributes: - - id: http.route - type: string - brief: | - The matched route (path template in the format used by the respective server framework). See note below - examples: - - /users/:userID? - - '{controller}/{action}/{id?}' - requirement_level: - conditionally_required: If and only if it's available - note: | - MUST NOT be populated when this is not supported by the HTTP server framework as the route attribute should have low-cardinality and the URI path can NOT substitute it. - SHOULD include the [application root](/docs/http/http-spans.md#http-server-definitions) if there is one. - tags: - sensitivity: PII - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: required - note: '' - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: http.request.method - type: - allow_custom_values: true - members: - - id: connect - value: CONNECT - brief: CONNECT method. - note: null - - id: delete - value: DELETE - brief: DELETE method. - note: null - - id: get - value: GET - brief: GET method. - note: null - - id: head - value: HEAD - brief: HEAD method. - note: null - - id: options - value: OPTIONS - brief: OPTIONS method. - note: null - - id: patch - value: PATCH - brief: PATCH method. - note: null - - id: post - value: POST - brief: POST method. - note: null - - id: put - value: PUT - brief: PUT method. - note: null - - id: trace - value: TRACE - brief: TRACE method. - note: null - - id: other - value: _OTHER - brief: Any HTTP method that the instrumentation has no prior knowledge of. - note: null - brief: HTTP request method. - examples: - - GET - - POST - - HEAD - requirement_level: required - note: | - HTTP request method value SHOULD be "known" to the instrumentation. - By default, this convention defines "known" methods as the ones listed in [RFC9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-methods) - and the PATCH method defined in [RFC5789](https://www.rfc-editor.org/rfc/rfc5789.html). - - If the HTTP request method is not known to instrumentation, it MUST set the `http.request.method` attribute to `_OTHER`. - - If the HTTP instrumentation could end up converting valid HTTP request methods to `_OTHER`, then it MUST provide a way to override - the list of known HTTP methods. If this override is done via environment variable, then the environment variable MUST be named - OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS and support a comma-separated list of case-sensitive known HTTP methods - (this list MUST be a full override of the default known method, it is not a list of known methods in addition to the defaults). - - HTTP method names are case-sensitive and `http.request.method` attribute value MUST match a known HTTP method name exactly. - Instrumentations for specific web frameworks that consider HTTP methods to be case insensitive, SHOULD populate a canonical equivalent. - Tracing instrumentations that do so, MUST also set `http.request.method_original` to the original value. - - id: mycompany.com.env - type: string - brief: The environment in which the service is running - requirement_level: required - note: '' - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - event_name: response - domain: http - attributes: - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: error.type - type: - allow_custom_values: true - members: - - id: other - value: _OTHER - brief: A fallback error value to be used when the instrumentation does not define a custom value for it. - note: null - brief: Describes a class of error the operation ended with. - examples: - - timeout - - java.net.UnknownHostException - - server_certificate_invalid - - '500' - requirement_level: recommended - note: | - The `error.type` SHOULD be predictable and SHOULD have low cardinality. - Instrumentations SHOULD document the list of errors they report. - - The cardinality of `error.type` within one instrumentation library SHOULD be low, but - telemetry consumers that aggregate data from multiple instrumentation libraries and applications - should be prepared for `error.type` to have high cardinality at query time, when no - additional filters are applied. - - If the operation has completed successfully, instrumentations SHOULD NOT set `error.type`. - - If a specific domain defines its own set of error codes (such as HTTP or gRPC status codes), - it's RECOMMENDED to use a domain-specific attribute and also set `error.type` to capture - all errors, regardless of whether they are defined within the domain-specific set or not. - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: http.response.status_code - type: int - brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).' - examples: - - 200 - requirement_level: required - note: Required status code for HTTP response events. - - id: http.route - type: string - brief: | - The matched route (path template in the format used by the respective server framework). See note below - examples: - - /users/:userID? - - '{controller}/{action}/{id?}' - requirement_level: - conditionally_required: If and only if it's available - note: | - MUST NOT be populated when this is not supported by the HTTP server framework as the route attribute should have low-cardinality and the URI path can NOT substitute it. - SHOULD include the [application root](/docs/http/http-spans.md#http-server-definitions) if there is one. - tags: - sensitivity: PII - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: required - note: '' - - id: mycompany.com.env - type: string - brief: The environment in which the service is running - requirement_level: required - note: '' - - id: http.request.method - type: - allow_custom_values: true - members: - - id: connect - value: CONNECT - brief: CONNECT method. - note: null - - id: delete - value: DELETE - brief: DELETE method. - note: null - - id: get - value: GET - brief: GET method. - note: null - - id: head - value: HEAD - brief: HEAD method. - note: null - - id: options - value: OPTIONS - brief: OPTIONS method. - note: null - - id: patch - value: PATCH - brief: PATCH method. - note: null - - id: post - value: POST - brief: POST method. - note: null - - id: put - value: PUT - brief: PUT method. - note: null - - id: trace - value: TRACE - brief: TRACE method. - note: null - - id: other - value: _OTHER - brief: Any HTTP method that the instrumentation has no prior knowledge of. - note: null - brief: HTTP request method. - examples: - - GET - - POST - - HEAD - requirement_level: required - note: | - HTTP request method value SHOULD be "known" to the instrumentation. - By default, this convention defines "known" methods as the ones listed in [RFC9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-methods) - and the PATCH method defined in [RFC5789](https://www.rfc-editor.org/rfc/rfc5789.html). - - If the HTTP request method is not known to instrumentation, it MUST set the `http.request.method` attribute to `_OTHER`. - - If the HTTP instrumentation could end up converting valid HTTP request methods to `_OTHER`, then it MUST provide a way to override - the list of known HTTP methods. If this override is done via environment variable, then the environment variable MUST be named - OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS and support a comma-separated list of case-sensitive known HTTP methods - (this list MUST be a full override of the default known method, it is not a list of known methods in addition to the defaults). - - HTTP method names are case-sensitive and `http.request.method` attribute value MUST match a known HTTP method name exactly. - Instrumentations for specific web frameworks that consider HTTP methods to be case insensitive, SHOULD populate a canonical equivalent. - Tracing instrumentations that do so, MUST also set `http.request.method_original` to the original value. - - event_name: error - domain: server - attributes: - - id: server.socket.address - type: string - brief: Server address of the socket connection - IP address or Unix domain socket name. - examples: - - 10.5.3.2 - requirement_level: - recommended: If different than `server.address`. - note: | - When observed from the client side, this SHOULD represent the immediate server peer address. - When observed from the server side, this SHOULD represent the physical server address. - - id: error.type - type: - allow_custom_values: true - members: - - id: other - value: _OTHER - brief: A fallback error value to be used when the instrumentation does not define a custom value for it. - note: null - brief: Describes a class of error the operation ended with. - examples: - - timeout - - java.net.UnknownHostException - - server_certificate_invalid - - '500' - requirement_level: recommended - note: | - The `error.type` SHOULD be predictable and SHOULD have low cardinality. - Instrumentations SHOULD document the list of errors they report. - - The cardinality of `error.type` within one instrumentation library SHOULD be low, but - telemetry consumers that aggregate data from multiple instrumentation libraries and applications - should be prepared for `error.type` to have high cardinality at query time, when no - additional filters are applied. - - If the operation has completed successfully, instrumentations SHOULD NOT set `error.type`. - - If a specific domain defines its own set of error codes (such as HTTP or gRPC status codes), - it's RECOMMENDED to use a domain-specific attribute and also set `error.type` to capture - all errors, regardless of whether they are defined within the domain-specific set or not. - - id: server.socket.domain - type: string - brief: Immediate server peer's domain name if available without reverse DNS lookup - examples: - - proxy.example.com - requirement_level: - recommended: If different than `server.address`. - note: Typically observed from the client side, and represents a proxy or other intermediary domain name. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.socket.port - type: int - brief: Server port number of the socket connection. - examples: - - 16456 - requirement_level: - recommended: If different than `server.port`. - note: | - When observed from the client side, this SHOULD represent the immediate server peer port. - When observed from the server side, this SHOULD represent the physical server port. -versions: - 1.4.0: - metrics: null - logs: null - spans: null - resources: null - 1.5.0: - metrics: null - logs: null - spans: null - resources: null - 1.6.1: - metrics: null - logs: null - spans: null - resources: null - 1.7.0: - metrics: null - logs: null - spans: null - resources: null - 1.8.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - db.cassandra.keyspace: db.name - db.hbase.namespace: db.name - resources: null - 1.9.0: - metrics: null - logs: null - spans: null - resources: null - 1.10.0: - metrics: null - logs: null - spans: null - resources: null - 1.11.0: - metrics: null - logs: null - spans: null - resources: null - 1.12.0: - metrics: null - logs: null - spans: null - resources: null - 1.13.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - net.host.ip: net.sock.host.addr - net.peer.ip: net.sock.peer.addr - resources: null - 1.14.0: - metrics: null - logs: null - spans: null - resources: null - 1.15.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - http.retry_count: http.resend_count - resources: null - 1.16.0: - metrics: null - logs: null - spans: null - resources: null - 1.17.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - messaging.destination: messaging.destination.name - messaging.rocketmq.message_keys: messaging.rocketmq.message.keys - messaging.message_payload_size_bytes: messaging.message.payload_size_bytes - messaging.message_payload_compressed_size_bytes: messaging.message.payload_compressed_size_bytes - messaging.message_id: messaging.message.id - messaging.kafka.tombstone: messaging.kafka.message.tombstone - messaging.protocol_version: net.app.protocol.version - messaging.destination_kind: messaging.destination.kind - messaging.conversation_id: messaging.message.conversation_id - messaging.kafka.message_key: messaging.kafka.message.key - messaging.rabbitmq.routing_key: messaging.rabbitmq.destination.routing_key - messaging.protocol: net.app.protocol.name - messaging.temp_destination: messaging.destination.temporary - messaging.rocketmq.message_type: messaging.rocketmq.message.type - messaging.consumer_id: messaging.consumer.id - messaging.rocketmq.message_tag: messaging.rocketmq.message.tag - messaging.kafka.partition: messaging.kafka.destination.partition - messaging.kafka.consumer_group: messaging.kafka.consumer.group - resources: null - 1.18.0: - metrics: null - logs: null - spans: null - resources: null - 1.19.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - faas.execution: faas.invocation_id - - rename_attributes: - attribute_map: - faas.id: cloud.resource_id - - rename_attributes: - attribute_map: - http.user_agent: user_agent.original - resources: - changes: - - rename_attributes: - attribute_map: - browser.user_agent: user_agent.original - 1.20.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - net.app.protocol.version: net.protocol.version - net.app.protocol.name: net.protocol.name - resources: null - 1.21.0: - metrics: - changes: - - rename_attributes: - attribute_map: {} - rename_metrics: - process.runtime.jvm.cpu.utilization: process.runtime.jvm.cpu.recent_utilization - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - messaging.kafka.client_id: messaging.client_id - messaging.rocketmq.client_id: messaging.client_id - - rename_attributes: - attribute_map: - http.client_ip: client.address - net.host.name: server.address - net.sock.peer.name: server.socket.domain - net.host.port: server.port - net.sock.host.addr: server.socket.address - net.sock.host.port: server.socket.port - - rename_attributes: - attribute_map: - net.host.carrier.icc: network.carrier.icc - net.protocol.version: network.protocol.version - net.host.connection.type: network.connection.type - net.host.carrier.mnc: network.carrier.mnc - net.host.carrier.mcc: network.carrier.mcc - net.host.connection.subtype: network.connection.subtype - net.protocol.name: network.protocol.name - net.host.carrier.name: network.carrier.name - - rename_attributes: - attribute_map: - http.method: http.request.method - http.url: url.full - http.response_content_length: http.response.body.size - http.scheme: url.scheme - http.status_code: http.response.status_code - http.request_content_length: http.request.body.size - resources: null diff --git a/data/resolved-schema-metrics.yaml b/data/resolved-schema-metrics.yaml deleted file mode 100644 index 779172ce..00000000 --- a/data/resolved-schema-metrics.yaml +++ /dev/null @@ -1,587 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: data/open-telemetry-schema.1.22.0.yaml -schema_url: https://mycompany.com/schemas/1.0.0 -schema: - resource: - attributes: - - id: os.build_id - type: string - brief: Unique identifier for a particular build or compilation of the operating system. - examples: - - TQ3C.230805.001.B2 - - '20E247' - - '22621' - requirement_level: recommended - note: '' - - id: os.name - type: string - brief: Human readable operating system name. - examples: - - iOS - - Android - - Ubuntu - requirement_level: recommended - note: '' - - id: os.description - type: string - brief: | - Human readable (not intended to be parsed) OS version information, like e.g. reported by `ver` or `lsb_release -a` commands. - examples: - - Microsoft Windows [Version 10.0.18363.778] - - Ubuntu 18.04.1 LTS - requirement_level: recommended - note: '' - - id: os.type - type: - allow_custom_values: true - members: - - id: windows - value: windows - brief: Microsoft Windows - note: null - - id: linux - value: linux - brief: Linux - note: null - - id: darwin - value: darwin - brief: Apple Darwin - note: null - - id: freebsd - value: freebsd - brief: FreeBSD - note: null - - id: netbsd - value: netbsd - brief: NetBSD - note: null - - id: openbsd - value: openbsd - brief: OpenBSD - note: null - - id: dragonflybsd - value: dragonflybsd - brief: DragonFly BSD - note: null - - id: hpux - value: hpux - brief: HP-UX (Hewlett Packard Unix) - note: null - - id: aix - value: aix - brief: AIX (Advanced Interactive eXecutive) - note: null - - id: solaris - value: solaris - brief: SunOS, Oracle Solaris - note: null - - id: z_os - value: z_os - brief: IBM z/OS - note: null - brief: The operating system type. - requirement_level: required - note: '' - - id: os.version - type: string - brief: | - The version string of the operating system as defined in [Version Attributes](/docs/resource/README.md#version-attributes). - examples: - - 14.2.1 - - 18.04.1 - requirement_level: recommended - note: '' - instrumentation_library: - name: my-service - version: 1.0.0 - resource_metrics: - attributes: - - id: environment - type: string - brief: The environment in which the service is running - tag: sensitive-information - requirement_level: required - note: '' - metrics: - - name: http.server.request.duration - brief: Duration of HTTP server requests. - note: '' - attributes: - - id: error.type - type: - allow_custom_values: true - members: - - id: other - value: _OTHER - brief: A fallback error value to be used when the instrumentation does not define a custom value for it. - note: null - brief: Describes a class of error the operation ended with. - examples: - - timeout - - java.net.UnknownHostException - - server_certificate_invalid - - '500' - requirement_level: recommended - note: | - The `error.type` SHOULD be predictable and SHOULD have low cardinality. - Instrumentations SHOULD document the list of errors they report. - - The cardinality of `error.type` within one instrumentation library SHOULD be low, but - telemetry consumers that aggregate data from multiple instrumentation libraries and applications - should be prepared for `error.type` to have high cardinality at query time, when no - additional filters are applied. - - If the operation has completed successfully, instrumentations SHOULD NOT set `error.type`. - - If a specific domain defines its own set of error codes (such as HTTP or gRPC status codes), - it's RECOMMENDED to use a domain-specific attribute and also set `error.type` to capture - all errors, regardless of whether they are defined within the domain-specific set or not. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - instrument: histogram - unit: s - metric_groups: - - id: http - attributes: - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: error.type - type: - allow_custom_values: true - members: - - id: other - value: _OTHER - brief: A fallback error value to be used when the instrumentation does not define a custom value for it. - note: null - brief: Describes a class of error the operation ended with. - examples: - - timeout - - java.net.UnknownHostException - - server_certificate_invalid - - '500' - requirement_level: recommended - note: | - The `error.type` SHOULD be predictable and SHOULD have low cardinality. - Instrumentations SHOULD document the list of errors they report. - - The cardinality of `error.type` within one instrumentation library SHOULD be low, but - telemetry consumers that aggregate data from multiple instrumentation libraries and applications - should be prepared for `error.type` to have high cardinality at query time, when no - additional filters are applied. - - If the operation has completed successfully, instrumentations SHOULD NOT set `error.type`. - - If a specific domain defines its own set of error codes (such as HTTP or gRPC status codes), - it's RECOMMENDED to use a domain-specific attribute and also set `error.type` to capture - all errors, regardless of whether they are defined within the domain-specific set or not. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - metrics: - - name: http.server.request.duration - brief: Duration of HTTP server requests. - note: '' - attributes: [] - instrument: histogram - unit: s - - name: http.server.request.body.size - brief: Size of HTTP server request bodies. - note: | - The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size. - attributes: [] - instrument: histogram - unit: By - - name: http.server.response.body.size - brief: Size of HTTP server response bodies. - note: | - The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size. - attributes: [] - instrument: histogram - unit: By -versions: - 1.4.0: - metrics: null - logs: null - spans: null - resources: null - 1.5.0: - metrics: null - logs: null - spans: null - resources: null - 1.6.1: - metrics: null - logs: null - spans: null - resources: null - 1.7.0: - metrics: null - logs: null - spans: null - resources: null - 1.8.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - db.cassandra.keyspace: db.name - db.hbase.namespace: db.name - resources: null - 1.9.0: - metrics: null - logs: null - spans: null - resources: null - 1.10.0: - metrics: null - logs: null - spans: null - resources: null - 1.11.0: - metrics: null - logs: null - spans: null - resources: null - 1.12.0: - metrics: null - logs: null - spans: null - resources: null - 1.13.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - net.host.ip: net.sock.host.addr - net.peer.ip: net.sock.peer.addr - resources: null - 1.14.0: - metrics: null - logs: null - spans: null - resources: null - 1.15.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - http.retry_count: http.resend_count - resources: null - 1.16.0: - metrics: null - logs: null - spans: null - resources: null - 1.17.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - messaging.rocketmq.message_type: messaging.rocketmq.message.type - messaging.destination_kind: messaging.destination.kind - messaging.conversation_id: messaging.message.conversation_id - messaging.rocketmq.message_keys: messaging.rocketmq.message.keys - messaging.message_payload_compressed_size_bytes: messaging.message.payload_compressed_size_bytes - messaging.kafka.tombstone: messaging.kafka.message.tombstone - messaging.temp_destination: messaging.destination.temporary - messaging.destination: messaging.destination.name - messaging.consumer_id: messaging.consumer.id - messaging.protocol: net.app.protocol.name - messaging.message_id: messaging.message.id - messaging.message_payload_size_bytes: messaging.message.payload_size_bytes - messaging.kafka.message_key: messaging.kafka.message.key - messaging.kafka.partition: messaging.kafka.destination.partition - messaging.rabbitmq.routing_key: messaging.rabbitmq.destination.routing_key - messaging.rocketmq.message_tag: messaging.rocketmq.message.tag - messaging.protocol_version: net.app.protocol.version - messaging.kafka.consumer_group: messaging.kafka.consumer.group - resources: null - 1.18.0: - metrics: null - logs: null - spans: null - resources: null - 1.19.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - faas.execution: faas.invocation_id - - rename_attributes: - attribute_map: - faas.id: cloud.resource_id - - rename_attributes: - attribute_map: - http.user_agent: user_agent.original - resources: - changes: - - rename_attributes: - attribute_map: - browser.user_agent: user_agent.original - 1.20.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - net.app.protocol.name: net.protocol.name - net.app.protocol.version: net.protocol.version - resources: null - 1.21.0: - metrics: - changes: - - rename_attributes: - attribute_map: {} - rename_metrics: - process.runtime.jvm.cpu.utilization: process.runtime.jvm.cpu.recent_utilization - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - messaging.kafka.client_id: messaging.client_id - messaging.rocketmq.client_id: messaging.client_id - - rename_attributes: - attribute_map: - net.host.name: server.address - net.sock.peer.name: server.socket.domain - net.sock.host.addr: server.socket.address - http.client_ip: client.address - net.host.port: server.port - net.sock.host.port: server.socket.port - - rename_attributes: - attribute_map: - net.protocol.name: network.protocol.name - net.host.carrier.icc: network.carrier.icc - net.host.carrier.mnc: network.carrier.mnc - net.host.carrier.name: network.carrier.name - net.host.carrier.mcc: network.carrier.mcc - net.protocol.version: network.protocol.version - net.host.connection.type: network.connection.type - net.host.connection.subtype: network.connection.subtype - - rename_attributes: - attribute_map: - http.url: url.full - http.request_content_length: http.request.body.size - http.response_content_length: http.response.body.size - http.status_code: http.response.status_code - http.scheme: url.scheme - http.method: http.request.method - resources: null - 1.22.0: - metrics: - changes: - - rename_attributes: - attribute_map: - messaging.message.payload_size_bytes: messaging.message.body.size - rename_metrics: {} - - rename_attributes: - attribute_map: {} - rename_metrics: - http.client.duration: http.client.request.duration - http.server.duration: http.server.request.duration - - rename_attributes: - attribute_map: {} - rename_metrics: - process.runtime.jvm.buffer.usage: jvm.buffer.memory.usage - process.runtime.jvm.classes.unloaded: jvm.class.unloaded - process.runtime.jvm.classes.current_loaded: jvm.class.count - process.runtime.jvm.system.cpu.utilization: jvm.system.cpu.utilization - process.runtime.jvm.memory.usage: jvm.memory.usage - process.runtime.jvm.gc.duration: jvm.gc.duration - process.runtime.jvm.memory.limit: jvm.memory.limit - process.runtime.jvm.system.cpu.load_1m: jvm.system.cpu.load_1m - process.runtime.jvm.cpu.recent_utilization: jvm.cpu.recent_utilization - process.runtime.jvm.buffer.limit: jvm.buffer.memory.limit - process.runtime.jvm.cpu.time: jvm.cpu.time - process.runtime.jvm.memory.usage_after_last_gc: jvm.memory.usage_after_last_gc - process.runtime.jvm.buffer.count: jvm.buffer.count - process.runtime.jvm.classes.loaded: jvm.class.loaded - process.runtime.jvm.memory.committed: jvm.memory.committed - process.runtime.jvm.memory.init: jvm.memory.init - process.runtime.jvm.threads.count: jvm.thread.count - - rename_attributes: - attribute_map: - pool: jvm.memory.pool.name - type: jvm.memory.type - apply_to_metrics: - - jvm.memory.usage - - jvm.memory.committed - - jvm.memory.limit - - jvm.memory.usage_after_last_gc - - jvm.memory.init - rename_metrics: {} - - rename_attributes: - attribute_map: - name: jvm.gc.name - action: jvm.gc.action - apply_to_metrics: - - jvm.gc.duration - rename_metrics: {} - - rename_attributes: - attribute_map: - daemon: thread.daemon - apply_to_metrics: - - jvm.threads.count - rename_metrics: {} - - rename_attributes: - attribute_map: - pool: jvm.buffer.pool.name - apply_to_metrics: - - jvm.buffer.usage - - jvm.buffer.limit - - jvm.buffer.count - rename_metrics: {} - - rename_attributes: - attribute_map: - cpu: system.cpu.logical_number - state: system.cpu.state - apply_to_metrics: - - system.cpu.time - - system.cpu.utilization - rename_metrics: {} - - rename_attributes: - attribute_map: - state: system.memory.state - apply_to_metrics: - - system.memory.usage - - system.memory.utilization - rename_metrics: {} - - rename_attributes: - attribute_map: - state: system.paging.state - apply_to_metrics: - - system.paging.usage - - system.paging.utilization - rename_metrics: {} - - rename_attributes: - attribute_map: - direction: system.paging.direction - type: system.paging.type - apply_to_metrics: - - system.paging.faults - - system.paging.operations - rename_metrics: {} - - rename_attributes: - attribute_map: - direction: system.disk.direction - device: system.device - apply_to_metrics: - - system.disk.io - - system.disk.operations - - system.disk.io_time - - system.disk.operation_time - - system.disk.merged - rename_metrics: {} - - rename_attributes: - attribute_map: - state: system.filesystem.state - device: system.device - type: system.filesystem.type - mode: system.filesystem.mode - mountpoint: system.filesystem.mountpoint - apply_to_metrics: - - system.filesystem.usage - - system.filesystem.utilization - rename_metrics: {} - - rename_attributes: - attribute_map: - protocol: network.protocol - direction: system.network.direction - device: system.device - state: system.network.state - apply_to_metrics: - - system.network.dropped - - system.network.packets - - system.network.errors - - system.network.io - - system.network.connections - rename_metrics: {} - - rename_attributes: - attribute_map: - status: system.processes.status - apply_to_metrics: - - system.processes.count - rename_metrics: {} - - rename_attributes: - attribute_map: {} - rename_metrics: - http.server.response.size: http.server.response.body.size - http.server.request.size: http.server.request.body.size - logs: null - spans: null - resources: null diff --git a/data/resolved-schema-spans.yaml b/data/resolved-schema-spans.yaml deleted file mode 100644 index 82d82543..00000000 --- a/data/resolved-schema-spans.yaml +++ /dev/null @@ -1,490 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: https://opentelemetry.io/schemas/1.21.0 -schema_url: https://mycompany.com/schemas/1.0.0 -schema: - resource: - attributes: - - id: os.version - type: string - brief: | - The version string of the operating system as defined in [Version Attributes](/docs/resource/README.md#version-attributes). - examples: - - 14.2.1 - - 18.04.1 - requirement_level: recommended - note: '' - - id: os.build_id - type: string - brief: Unique identifier for a particular build or compilation of the operating system. - examples: - - TQ3C.230805.001.B2 - - '20E247' - - '22621' - requirement_level: recommended - note: '' - - id: os.name - type: string - brief: Human readable operating system name. - examples: - - iOS - - Android - - Ubuntu - requirement_level: recommended - note: '' - - id: os.type - type: - allow_custom_values: true - members: - - id: windows - value: windows - brief: Microsoft Windows - note: null - - id: linux - value: linux - brief: Linux - note: null - - id: darwin - value: darwin - brief: Apple Darwin - note: null - - id: freebsd - value: freebsd - brief: FreeBSD - note: null - - id: netbsd - value: netbsd - brief: NetBSD - note: null - - id: openbsd - value: openbsd - brief: OpenBSD - note: null - - id: dragonflybsd - value: dragonflybsd - brief: DragonFly BSD - note: null - - id: hpux - value: hpux - brief: HP-UX (Hewlett Packard Unix) - note: null - - id: aix - value: aix - brief: AIX (Advanced Interactive eXecutive) - note: null - - id: solaris - value: solaris - brief: SunOS, Oracle Solaris - note: null - - id: z_os - value: z_os - brief: IBM z/OS - note: null - brief: The operating system type. - requirement_level: required - note: '' - - id: os.description - type: string - brief: | - Human readable (not intended to be parsed) OS version information, like e.g. reported by `ver` or `lsb_release -a` commands. - examples: - - Microsoft Windows [Version 10.0.18363.778] - - Ubuntu 18.04.1 LTS - requirement_level: recommended - note: '' - instrumentation_library: - name: my-service - version: 1.0.0 - resource_spans: - spans: - - span_name: client.http.request - attributes: - - id: client.socket.address - type: string - brief: Client address of the socket connection - IP address or Unix domain socket name. - examples: - - /tmp/my.sock - - 127.0.0.1 - requirement_level: - recommended: If different than `client.address`. - note: | - When observed from the server side, this SHOULD represent the immediate client peer address. - When observed from the client side, this SHOULD represent the physical client address. - - id: client.address - type: string - brief: Client address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - /tmp/my.sock - - 10.1.2.80 - requirement_level: recommended - note: | - When observed from the server side, and when communicating through an intermediary, `client.address` SHOULD represent the client address behind any intermediaries (e.g. proxies) if it's available. - - id: client.socket.port - type: int - brief: Client port number of the socket connection. - examples: - - 35555 - requirement_level: - recommended: If different than `client.port`. - note: | - When observed from the server side, this SHOULD represent the immediate client peer port. - When observed from the client side, this SHOULD represent the physical client port. - - id: client.port - type: int - brief: Client port number. - examples: - - 65123 - requirement_level: recommended - note: | - When observed from the server side, and when communicating through an intermediary, `client.port` SHOULD represent the client port behind any intermediaries (e.g. proxies) if it's available. - events: - - event_name: error - attributes: - - id: exception.message - type: string - brief: The exception message. - examples: - - Division by zero - - Can't convert 'int' object to str implicitly - requirement_level: recommended - note: '' - - id: exception.type - type: string - brief: | - The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. - examples: - - java.net.ConnectException - - OSError - requirement_level: recommended - note: '' - - id: exception.stacktrace - type: string - brief: | - A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. - examples: 'Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5)' - requirement_level: recommended - note: '' - - event_name: feature_flag - attributes: - - id: feature_flag.variant - type: string - brief: | - SHOULD be a semantic identifier for a value. If one is unavailable, a stringified version of the value can be used. - examples: - - red - - 'true' - - on - requirement_level: recommended - note: |- - A semantic identifier, commonly referred to as a variant, provides a means - for referring to a value without including the value itself. This can - provide additional context for understanding the meaning behind a value. - For example, the variant `red` maybe be used for the value `#c05543`. - - A stringified version of the value can be used in situations where a - semantic identifier is unavailable. String representation of the value - should be determined by the implementer. - - id: feature_flag.provider_name - type: string - brief: The name of the service provider that performs the flag evaluation. - examples: - - Flag Manager - requirement_level: recommended - note: '' - - id: feature_flag.key - type: string - brief: The unique identifier of the feature flag. - examples: - - logo-color - requirement_level: required - note: '' - - span_name: server.http.request - attributes: - - id: server.socket.address - type: string - brief: Server address of the socket connection - IP address or Unix domain socket name. - examples: - - 10.5.3.2 - requirement_level: - recommended: If different than `server.address`. - note: | - When observed from the client side, this SHOULD represent the immediate server peer address. - When observed from the server side, this SHOULD represent the physical server address. - - id: server.socket.domain - type: string - brief: Immediate server peer's domain name if available without reverse DNS lookup - examples: - - proxy.example.com - requirement_level: - recommended: If different than `server.address`. - note: Typically observed from the client side, and represents a proxy or other intermediary domain name. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.socket.port - type: int - brief: Server port number of the socket connection. - examples: - - 16456 - requirement_level: - recommended: If different than `server.port`. - note: | - When observed from the client side, this SHOULD represent the immediate server peer port. - When observed from the server side, this SHOULD represent the physical server port. - events: - - event_name: error - attributes: - - id: exception.stacktrace - type: string - brief: | - A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. - examples: 'Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5)' - requirement_level: recommended - note: '' - - id: exception.type - type: string - brief: | - The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. - examples: - - java.net.ConnectException - - OSError - requirement_level: recommended - note: '' - - id: exception.message - type: string - brief: The exception message. - examples: - - Division by zero - - Can't convert 'int' object to str implicitly - requirement_level: recommended - note: '' - - event_name: feature_flag - attributes: - - id: feature_flag.key - type: string - brief: The unique identifier of the feature flag. - examples: - - logo-color - requirement_level: required - note: '' - - id: feature_flag.variant - type: string - brief: | - SHOULD be a semantic identifier for a value. If one is unavailable, a stringified version of the value can be used. - examples: - - red - - 'true' - - on - requirement_level: recommended - note: |- - A semantic identifier, commonly referred to as a variant, provides a means - for referring to a value without including the value itself. This can - provide additional context for understanding the meaning behind a value. - For example, the variant `red` maybe be used for the value `#c05543`. - - A stringified version of the value can be used in situations where a - semantic identifier is unavailable. String representation of the value - should be determined by the implementer. - - id: feature_flag.provider_name - type: string - brief: The name of the service provider that performs the flag evaluation. - examples: - - Flag Manager - requirement_level: recommended - note: '' -versions: - 1.4.0: - metrics: null - logs: null - spans: null - resources: null - 1.5.0: - metrics: null - logs: null - spans: null - resources: null - 1.6.1: - metrics: null - logs: null - spans: null - resources: null - 1.7.0: - metrics: null - logs: null - spans: null - resources: null - 1.8.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - db.hbase.namespace: db.name - db.cassandra.keyspace: db.name - resources: null - 1.9.0: - metrics: null - logs: null - spans: null - resources: null - 1.10.0: - metrics: null - logs: null - spans: null - resources: null - 1.11.0: - metrics: null - logs: null - spans: null - resources: null - 1.12.0: - metrics: null - logs: null - spans: null - resources: null - 1.13.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - net.peer.ip: net.sock.peer.addr - net.host.ip: net.sock.host.addr - resources: null - 1.14.0: - metrics: null - logs: null - spans: null - resources: null - 1.15.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - http.retry_count: http.resend_count - resources: null - 1.16.0: - metrics: null - logs: null - spans: null - resources: null - 1.17.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - messaging.conversation_id: messaging.message.conversation_id - messaging.consumer_id: messaging.consumer.id - messaging.destination: messaging.destination.name - messaging.rabbitmq.routing_key: messaging.rabbitmq.destination.routing_key - messaging.message_id: messaging.message.id - messaging.kafka.message_key: messaging.kafka.message.key - messaging.message_payload_size_bytes: messaging.message.payload_size_bytes - messaging.rocketmq.message_tag: messaging.rocketmq.message.tag - messaging.kafka.partition: messaging.kafka.destination.partition - messaging.protocol: net.app.protocol.name - messaging.rocketmq.message_type: messaging.rocketmq.message.type - messaging.message_payload_compressed_size_bytes: messaging.message.payload_compressed_size_bytes - messaging.protocol_version: net.app.protocol.version - messaging.kafka.tombstone: messaging.kafka.message.tombstone - messaging.kafka.consumer_group: messaging.kafka.consumer.group - messaging.temp_destination: messaging.destination.temporary - messaging.destination_kind: messaging.destination.kind - messaging.rocketmq.message_keys: messaging.rocketmq.message.keys - resources: null - 1.18.0: - metrics: null - logs: null - spans: null - resources: null - 1.19.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - faas.execution: faas.invocation_id - - rename_attributes: - attribute_map: - faas.id: cloud.resource_id - - rename_attributes: - attribute_map: - http.user_agent: user_agent.original - resources: - changes: - - rename_attributes: - attribute_map: - browser.user_agent: user_agent.original - 1.20.0: - metrics: null - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - net.app.protocol.name: net.protocol.name - net.app.protocol.version: net.protocol.version - resources: null - 1.21.0: - metrics: - changes: - - rename_attributes: - attribute_map: {} - rename_metrics: - process.runtime.jvm.cpu.utilization: process.runtime.jvm.cpu.recent_utilization - logs: null - spans: - changes: - - rename_attributes: - attribute_map: - messaging.kafka.client_id: messaging.client_id - messaging.rocketmq.client_id: messaging.client_id - - rename_attributes: - attribute_map: - net.sock.host.addr: server.socket.address - net.sock.host.port: server.socket.port - http.client_ip: client.address - net.sock.peer.name: server.socket.domain - net.host.name: server.address - net.host.port: server.port - - rename_attributes: - attribute_map: - net.host.carrier.icc: network.carrier.icc - net.protocol.name: network.protocol.name - net.host.connection.type: network.connection.type - net.host.carrier.mcc: network.carrier.mcc - net.host.carrier.name: network.carrier.name - net.protocol.version: network.protocol.version - net.host.connection.subtype: network.connection.subtype - net.host.carrier.mnc: network.carrier.mnc - - rename_attributes: - attribute_map: - http.status_code: http.response.status_code - http.url: url.full - http.request_content_length: http.request.body.size - http.response_content_length: http.response.body.size - http.method: http.request.method - http.scheme: url.scheme - resources: null diff --git a/data/resolved_schema.yaml b/data/resolved_schema.yaml deleted file mode 100644 index 2dc0011b..00000000 --- a/data/resolved_schema.yaml +++ /dev/null @@ -1,561 +0,0 @@ -file_format: 1.2.0 -parent_schema_url: data/open-telemetry-schema.1.22.0.yaml -schema_url: https://mycompany.com/schemas/1.0.0 -semantic_conventions: -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/url.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/http-common.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/client.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/general.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/exception.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/server.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/network.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/http.yaml -- url: https://raw.githubusercontent.com/open-telemetry/semantic-conventions/main/model/metrics/jvm-metrics.yaml -schema: - resource: - attributes: - - ref: service.name - requirement_level: recommended - note: '' - value: my-service - - ref: service.version - requirement_level: recommended - note: '' - value: '{{SERVICE_VERSION}}' - instrumentation_library: - name: my-service - version: 1.0.0 - resource_metrics: - attributes: - - id: environment - type: string - brief: The environment in which the service is running - examples: null - tag: sensitive-information - requirement_level: required - note: '' - metrics: - - name: jvm.thread.count - brief: Number of executing platform threads. - note: '' - attributes: - - id: thread.daemon - type: boolean - brief: Whether the thread is daemon or not. - examples: null - requirement_level: recommended - note: '' - instrument: updowncounter - unit: '{thread}' - - name: jvm.class.loaded - brief: Number of classes loaded since JVM start. - note: '' - attributes: [] - instrument: counter - unit: '{class}' - - name: jvm.cpu.recent_utilization - brief: Recent CPU utilization for the process as reported by the JVM. - note: | - The value range is [0.0,1.0]. This utilization is not defined as being for the specific interval since last measurement (unlike `system.cpu.utilization`). [Reference](https://docs.oracle.com/en/java/javase/17/docs/api/jdk.management/com/sun/management/OperatingSystemMXBean.html#getProcessCpuLoad()). - attributes: [] - instrument: gauge - unit: '1' - - name: http.server.request.duration - brief: Measures the duration of inbound HTTP requests. - note: '' - attributes: - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: http.response.status_code - type: int - brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).' - examples: - - 200 - requirement_level: - conditionally_required: If and only if one was received/sent. - note: '' - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: recommended - note: '' - instrument: histogram - unit: s - metric_groups: - - id: http - attributes: - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: http.response.status_code - type: int - brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).' - examples: - - 200 - requirement_level: - conditionally_required: If and only if one was received/sent. - note: '' - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: recommended - note: '' - - id: url.host - type: string - brief: The host of the request - examples: null - requirement_level: required - note: '' - metrics: - - name: jvm.thread.count - brief: Number of executing platform threads. - note: '' - attributes: - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: http.response.status_code - type: int - brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).' - examples: - - 200 - requirement_level: - conditionally_required: If and only if one was received/sent. - note: '' - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: recommended - note: '' - - id: url.host - type: string - brief: The host of the request - examples: null - requirement_level: required - note: '' - instrument: updowncounter - unit: '{thread}' - - name: jvm.class.loaded - brief: Number of classes loaded since JVM start. - note: '' - attributes: - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: http.response.status_code - type: int - brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).' - examples: - - 200 - requirement_level: - conditionally_required: If and only if one was received/sent. - note: '' - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: recommended - note: '' - - id: url.host - type: string - brief: The host of the request - examples: null - requirement_level: required - note: '' - instrument: counter - unit: '{class}' - - name: jvm.cpu.recent_utilization - brief: Recent CPU utilization for the process as reported by the JVM. - note: | - The value range is [0.0,1.0]. This utilization is not defined as being for the specific interval since last measurement (unlike `system.cpu.utilization`). [Reference](https://docs.oracle.com/en/java/javase/17/docs/api/jdk.management/com/sun/management/OperatingSystemMXBean.html#getProcessCpuLoad()). - attributes: - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: http.response.status_code - type: int - brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).' - examples: - - 200 - requirement_level: - conditionally_required: If and only if one was received/sent. - note: '' - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: recommended - note: '' - - id: url.host - type: string - brief: The host of the request - examples: null - requirement_level: required - note: '' - instrument: gauge - unit: '1' - resource_events: - events: - - event_name: http - domain: http - attributes: - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: http.response.status_code - type: int - brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).' - examples: - - 200 - requirement_level: - conditionally_required: If and only if one was received/sent. - note: '' - - id: network.protocol.name - type: string - brief: '[OSI application layer](https://osi-model.com/application-layer/) or non-OSI equivalent.' - examples: - - amqp - - http - - mqtt - requirement_level: recommended - note: The value SHOULD be normalized to lowercase. - - id: network.protocol.version - type: string - brief: Version of the protocol specified in `network.protocol.name`. - examples: 3.1.1 - requirement_level: recommended - note: | - `network.protocol.version` refers to the version of the protocol used and might be different from the protocol client's version. If the HTTP client used has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should be set to `1.1`. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: recommended - note: '' - - id: url.host - type: string - brief: The host of the request - examples: null - requirement_level: required - note: '' - resource_spans: - spans: - - span_name: http.request - attributes: - - id: server.address - type: string - brief: Server address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - example.com - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent - the server address behind any intermediaries (e.g. proxies) if it's available. - - id: server.port - type: int - brief: Server port number - examples: - - 80 - - 8080 - - 443 - requirement_level: recommended - note: | - When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries (e.g. proxies) if it's available. - - id: server.socket.address - type: string - brief: Server address of the socket connection - IP address or Unix domain socket name. - examples: - - 10.5.3.2 - requirement_level: - recommended: If different than `server.address`. - note: | - When observed from the client side, this SHOULD represent the immediate server peer address. - When observed from the server side, this SHOULD represent the physical server address. - - id: server.socket.port - type: int - brief: Server port number of the socket connection. - examples: - - 16456 - requirement_level: - recommended: If different than `server.port`. - note: | - When observed from the client side, this SHOULD represent the immediate server peer port. - When observed from the server side, this SHOULD represent the physical server port. - - id: client.address - type: string - brief: Client address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name. - examples: - - /tmp/my.sock - - 10.1.2.80 - requirement_level: recommended - note: | - When observed from the server side, and when communicating through an intermediary, `client.address` SHOULD represent the client address behind any intermediaries (e.g. proxies) if it's available. - - id: client.port - type: int - brief: Client port number. - examples: - - 65123 - requirement_level: recommended - note: | - When observed from the server side, and when communicating through an intermediary, `client.port` SHOULD represent the client port behind any intermediaries (e.g. proxies) if it's available. - - id: client.socket.address - type: string - brief: Client address of the socket connection - IP address or Unix domain socket name. - examples: - - /tmp/my.sock - - 127.0.0.1 - requirement_level: - recommended: If different than `client.address`. - note: | - When observed from the server side, this SHOULD represent the immediate client peer address. - When observed from the client side, this SHOULD represent the physical client address. - - id: client.socket.port - type: int - brief: Client port number of the socket connection. - examples: - - 35555 - requirement_level: - recommended: If different than `client.port`. - note: | - When observed from the server side, this SHOULD represent the immediate client peer port. - When observed from the client side, this SHOULD represent the physical client port. - - id: url.scheme - type: string - brief: The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. - examples: - - https - - ftp - - telnet - requirement_level: recommended - note: '' - - id: url.host - type: string - brief: The host of the request - examples: null - requirement_level: required - note: '' - events: - - event_name: error - attributes: - - id: exception.type - type: string - brief: | - The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. - examples: - - java.net.ConnectException - - OSError - requirement_level: recommended - note: '' - - id: exception.message - type: string - brief: The exception message. - examples: - - Division by zero - - Can't convert 'int' object to str implicitly - requirement_level: recommended - note: '' - - id: exception.stacktrace - type: string - brief: | - A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. - examples: 'Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5)' - requirement_level: recommended - note: '' diff --git a/src/registry/check.rs b/src/registry/check.rs index 9335d380..45014bb0 100644 --- a/src/registry/check.rs +++ b/src/registry/check.rs @@ -2,7 +2,7 @@ //! Check a semantic convention registry. -use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, RegistryPath}; +use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, RegistryPath, semconv_registry_path_from}; use clap::Args; use std::path::PathBuf; use weaver_cache::Cache; @@ -26,9 +26,9 @@ pub struct RegistryCheckArgs { pub registry_git_sub_dir: Option, /// Optional list of policy files to check against the files of the semantic - /// convention registry before the resolution process. - #[arg(short = 'b', long)] - pub before_resolution_policies: Vec, + /// convention registry. + #[arg(short = 'p', long)] + pub policies: Vec, } /// Check a semantic convention registry. @@ -37,18 +37,23 @@ pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: & logger.loading(&format!("Checking registry `{}`", args.registry)); let registry_id = "default"; + let registry_path = semconv_registry_path_from( + &args.registry, + &args.registry_git_sub_dir, + ); // Load the semantic convention registry into a local cache. // No parsing errors should be observed. let semconv_specs = load_semconv_specs( - &args.registry, - &args.registry_git_sub_dir, + ®istry_path, cache, logger.clone(), ); check_policies( - &args.before_resolution_policies, + ®istry_path, + cache, + &args.policies, &semconv_specs, logger.clone(), ); diff --git a/src/registry/generate.rs b/src/registry/generate.rs index 5a76697e..7b707c5e 100644 --- a/src/registry/generate.rs +++ b/src/registry/generate.rs @@ -13,7 +13,7 @@ use weaver_forge::registry::TemplateRegistry; use weaver_forge::{GeneratorConfig, TemplateEngine}; use weaver_semconv::registry::SemConvRegistry; -use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, RegistryPath}; +use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, RegistryPath, semconv_registry_path_from}; /// Parameters for the `registry generate` sub-command #[derive(Debug, Args)] @@ -63,15 +63,20 @@ pub(crate) fn command( )); let registry_id = "default"; + let registry_path = semconv_registry_path_from( + &args.registry, + &args.registry_git_sub_dir, + ); // Load the semantic convention registry into a local cache. let semconv_specs = load_semconv_specs( - &args.registry, - &args.registry_git_sub_dir, + ®istry_path, cache, logger.clone(), ); check_policies( + ®istry_path, + cache, &args.before_resolution_policies, &semconv_specs, logger.clone(), diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 881f810a..07d58f42 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -2,17 +2,18 @@ //! Commands to manage a semantic convention registry. -use clap::{Args, Subcommand}; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::fmt::Display; +use std::path::PathBuf; use std::str::FromStr; +use clap::{Args, Subcommand}; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; + use check::RegistryCheckArgs; -use std::path::PathBuf; use weaver_cache::Cache; +use weaver_checker::{Engine, Error, PolicyPackage}; use weaver_checker::Error::{InvalidPolicyFile, PolicyViolation}; -use weaver_checker::{Engine, Error}; -use weaver_common::error::{handle_errors, ExitIfError}; +use weaver_common::error::{ExitIfError, handle_errors}; use weaver_common::Logger; use weaver_resolved_schema::ResolvedTelemetrySchema; use weaver_resolver::SchemaResolver; @@ -97,9 +98,9 @@ impl Display for RegistryPath { pub struct RegistryArgs { /// Local path or Git URL of the semantic convention registry. #[arg( - short = 'r', - long, - default_value = "https://github.com/open-telemetry/semantic-conventions.git" + short = 'r', + long, + default_value = "https://github.com/open-telemetry/semantic-conventions.git" )] pub registry: RegistryPath, @@ -156,12 +157,10 @@ pub(crate) fn semconv_registry_path_from( /// * `log` - The logger to use for logging messages. #[cfg(not(tarpaulin_include))] pub(crate) fn load_semconv_specs( - registry: &RegistryPath, - path: &Option, + registry_path: &weaver_semconv::path::RegistryPath, cache: &Cache, log: impl Logger + Sync + Clone, ) -> Vec<(String, SemConvSpec)> { - let registry_path = semconv_registry_path_from(registry, path); let semconv_specs = SchemaResolver::load_semconv_specs(®istry_path, cache).exit_if_error(log.clone()); log.success(&format!( @@ -192,7 +191,7 @@ pub fn check_policy( let mut errors = vec![]; match policy_engine.set_input(semconv) { - Ok(_) => match policy_engine.check() { + Ok(_) => match policy_engine.check(PolicyPackage::BeforeResolution) { Ok(violations) => { for violation in violations { errors.push(PolicyViolation { @@ -223,22 +222,35 @@ pub fn check_policy( /// /// # Arguments /// -/// * `before_resolution_policies` - The list of policy files to check before the resolution process. +/// * `policies` - The list of policy files to check. /// * `semconv_specs` - The semantic convention specifications to check. /// * `logger` - The logger to use for logging messages. #[cfg(not(tarpaulin_include))] fn check_policies( - before_resolution_policies: &[PathBuf], + registry_path: &weaver_semconv::path::RegistryPath, + cache: &Cache, + policies: &[PathBuf], semconv_specs: &[(String, SemConvSpec)], logger: impl Logger + Sync + Clone, ) { - if !before_resolution_policies.is_empty() { - let mut engine = Engine::new(); - for policy in before_resolution_policies { - engine.add_policy(policy).exit_if_error(logger.clone()); - } + let mut engine = Engine::new(); + + // Add policies from the registry + let (registry_path, _) = SchemaResolver::path_to_registry(registry_path, cache) + .exit_if_error(logger.clone()); + let added_policies_count = engine.add_policies(registry_path.as_path(), "*.rego") + .exit_if_error(logger.clone()); + + // Add policies from the command line + for policy in policies { + engine.add_policy(policy).exit_if_error(logger.clone()); + } + + if added_policies_count + policies.len() > 0 { check_policy(&engine, semconv_specs).exit_if_error(logger.clone()); logger.success("Policies checked"); + } else { + logger.success("No policy found"); } } diff --git a/src/registry/resolve.rs b/src/registry/resolve.rs index 33ff455a..07929915 100644 --- a/src/registry/resolve.rs +++ b/src/registry/resolve.rs @@ -12,7 +12,7 @@ use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; use weaver_semconv::registry::SemConvRegistry; -use crate::registry::{load_semconv_specs, resolve_semconv_specs, RegistryArgs}; +use crate::registry::{load_semconv_specs, resolve_semconv_specs, RegistryArgs, semconv_registry_path_from}; /// Supported output formats for the resolved schema #[derive(Debug, Clone, ValueEnum)] @@ -64,11 +64,14 @@ pub(crate) fn command( logger.loading(&format!("Resolving registry `{}`", args.registry.registry)); let registry_id = "default"; + let registry_path = semconv_registry_path_from( + &args.registry.registry, + &args.registry.registry_git_sub_dir + ); // Load the semantic convention registry into a local cache. let semconv_specs = load_semconv_specs( - &args.registry.registry, - &args.registry.registry_git_sub_dir, + ®istry_path, cache, logger.clone(), ); diff --git a/src/registry/stats.rs b/src/registry/stats.rs index 1c4a33ba..5850af8d 100644 --- a/src/registry/stats.rs +++ b/src/registry/stats.rs @@ -2,7 +2,7 @@ //! Compute stats on a semantic convention registry. -use crate::registry::{load_semconv_specs, resolve_semconv_specs, RegistryArgs}; +use crate::registry::{load_semconv_specs, resolve_semconv_specs, RegistryArgs, semconv_registry_path_from}; use clap::Args; use weaver_cache::Cache; use weaver_common::Logger; @@ -28,11 +28,14 @@ pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: & )); let registry_id = "default"; + let registry_path = semconv_registry_path_from( + &args.registry.registry, + &args.registry.registry_git_sub_dir, + ); // Load the semantic convention registry into a local cache. let semconv_specs = load_semconv_specs( - &args.registry.registry, - &args.registry.registry_git_sub_dir, + ®istry_path, cache, logger.clone(), ); From f7ebacbc3ed36d6a942c642a55d4bbbb221a13e7 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Mon, 13 May 2024 23:41:18 -0700 Subject: [PATCH 02/17] chore(diagnostic): Render diagnostics from Jinja templates. --- Cargo.lock | 11 + Cargo.toml | 3 + crates/weaver_cache/Cargo.toml | 1 + crates/weaver_cache/src/lib.rs | 3 +- crates/weaver_checker/Cargo.toml | 2 + crates/weaver_checker/src/lib.rs | 11 +- crates/weaver_common/Cargo.toml | 4 +- crates/weaver_common/src/diag/mod.rs | 5 + crates/weaver_common/src/diagnostic.rs | 100 ++++++++ crates/weaver_common/src/error.rs | 17 +- crates/weaver_common/src/lib.rs | 1 + crates/weaver_forge/Cargo.toml | 1 + crates/weaver_forge/src/error.rs | 4 +- .../weaver_forge/src/extensions/ansi_code.rs | 221 ++++++++++++++++++ crates/weaver_forge/src/extensions/mod.rs | 1 + crates/weaver_forge/src/lib.rs | 114 +++++++-- crates/weaver_resolver/Cargo.toml | 1 + crates/weaver_resolver/src/lib.rs | 5 +- crates/weaver_semconv/Cargo.toml | 1 + crates/weaver_semconv/src/lib.rs | 4 +- crates/weaver_semconv_gen/Cargo.toml | 1 + crates/weaver_semconv_gen/src/gen.rs | 138 +++++------ crates/weaver_semconv_gen/src/lib.rs | 18 +- src/main.rs | 16 +- src/registry/check.rs | 25 +- src/registry/generate.rs | 19 +- src/registry/mod.rs | 79 +++++-- src/registry/resolve.rs | 3 +- src/registry/stats.rs | 3 +- templates/errors/console/errors.txt.j2 | 15 ++ templates/errors/console/weaver.yaml | 4 + .../errors/gh_workflow_command/errors.txt.j2 | 1 + .../errors/gh_workflow_command/weaver.yaml | 4 + templates/errors/json/errors.json.j2 | 2 + templates/errors/json/weaver.yaml | 4 + 35 files changed, 702 insertions(+), 140 deletions(-) create mode 100644 crates/weaver_common/src/diagnostic.rs create mode 100644 crates/weaver_forge/src/extensions/ansi_code.rs create mode 100644 templates/errors/console/errors.txt.j2 create mode 100644 templates/errors/console/weaver.yaml create mode 100644 templates/errors/gh_workflow_command/errors.txt.j2 create mode 100644 templates/errors/gh_workflow_command/weaver.yaml create mode 100644 templates/errors/json/errors.json.j2 create mode 100644 templates/errors/json/weaver.yaml diff --git a/Cargo.lock b/Cargo.lock index 8a0bda44..9e186526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2067,6 +2067,7 @@ dependencies = [ "cfg-if", "miette-derive", "owo-colors", + "serde", "supports-color", "supports-hyperlinks", "supports-unicode", @@ -3596,6 +3597,7 @@ name = "weaver" version = "0.1.0" dependencies = [ "clap", + "miette", "rayon", "serde", "serde_json", @@ -3617,6 +3619,7 @@ version = "0.1.0" dependencies = [ "dirs", "gix", + "serde", "tempdir", "thiserror", ] @@ -3626,6 +3629,7 @@ name = "weaver_checker" version = "0.1.0" dependencies = [ "globset", + "miette", "regorus", "serde", "serde_json", @@ -3633,6 +3637,7 @@ dependencies = [ "thiserror", "walkdir", "weaver_common", + "weaver_forge", ] [[package]] @@ -3654,6 +3659,8 @@ version = "0.1.0" dependencies = [ "miette", "paris", + "serde", + "serde_json", "thiserror", ] @@ -3676,6 +3683,7 @@ dependencies = [ "jaq-interpret", "jaq-parse", "jaq-std", + "miette", "minijinja", "opentelemetry", "opentelemetry-stdout", @@ -3711,6 +3719,7 @@ name = "weaver_resolver" version = "0.1.0" dependencies = [ "glob", + "miette", "rayon", "serde", "serde_json", @@ -3728,6 +3737,7 @@ name = "weaver_semconv" version = "0.1.0" dependencies = [ "glob", + "miette", "ordered-float", "serde", "serde_yaml", @@ -3741,6 +3751,7 @@ name = "weaver_semconv_gen" version = "0.1.0" dependencies = [ "itertools 0.12.1", + "miette", "nom", "serde", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 49b78edc..a9dfcde8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ walkdir = "2.5.0" anyhow = "1.0.83" itertools = "0.12.1" globset = { version = "0.4.14", features = ["serde1"] } +miette = { version = "7.2.0", features = ["fancy", "serde"] } # Features definition ========================================================= [features] @@ -68,6 +69,8 @@ serde.workspace = true serde_yaml.workspace = true serde_json.workspace = true walkdir.workspace = true +miette.workspace = true + rayon = "1.10.0" [profile.release] diff --git a/crates/weaver_cache/Cargo.toml b/crates/weaver_cache/Cargo.toml index 9886ac72..b5b61001 100644 --- a/crates/weaver_cache/Cargo.toml +++ b/crates/weaver_cache/Cargo.toml @@ -23,4 +23,5 @@ gix = { version = "0.62.0", default-features = false, features = [ ] } thiserror.workspace = true +serde.workspace = true diff --git a/crates/weaver_cache/src/lib.rs b/crates/weaver_cache/src/lib.rs index e0a7c35b..086f4561 100644 --- a/crates/weaver_cache/src/lib.rs +++ b/crates/weaver_cache/src/lib.rs @@ -18,9 +18,10 @@ use gix::create::Kind; use gix::remote::fetch::Shallow; use gix::{create, open, progress}; use tempdir::TempDir; +use serde::Serialize; /// An error that can occur while creating or using a cache. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Serialize)] #[non_exhaustive] pub enum Error { /// Home directory not found. diff --git a/crates/weaver_checker/Cargo.toml b/crates/weaver_checker/Cargo.toml index 03c1ea63..096a7c27 100644 --- a/crates/weaver_checker/Cargo.toml +++ b/crates/weaver_checker/Cargo.toml @@ -15,6 +15,7 @@ workspace = true [dependencies] weaver_common = { path = "../weaver_common" } +weaver_forge = { path = "../weaver_forge" } thiserror.workspace = true serde.workspace = true @@ -22,6 +23,7 @@ serde_json.workspace = true serde_yaml.workspace = true walkdir.workspace = true globset.workspace = true +miette.workspace = true regorus = { version = "0.1.4", default-features = false, features = [ "arc", diff --git a/crates/weaver_checker/src/lib.rs b/crates/weaver_checker/src/lib.rs index 3da573aa..79b75ef0 100644 --- a/crates/weaver_checker/src/lib.rs +++ b/crates/weaver_checker/src/lib.rs @@ -6,6 +6,7 @@ use std::fmt::{Display, Formatter}; use std::path::Path; use globset::Glob; +use miette::Diagnostic; use serde::Serialize; use serde_json::to_value; use walkdir::DirEntry; @@ -18,12 +19,14 @@ use crate::violation::Violation; pub mod violation; /// An error that can occur while evaluating policies. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Serialize, Diagnostic, Clone)] #[must_use] #[non_exhaustive] +#[serde(tag = "type", rename_all = "snake_case")] pub enum Error { /// An invalid policy. #[error("Invalid policy file '{file}', error: {error})")] + #[diagnostic()] InvalidPolicyFile { /// The file that caused the error. file: String, @@ -33,6 +36,7 @@ pub enum Error { /// An invalid policy glob pattern. #[error("Invalid policy glob pattern '{pattern}', error: {error})")] + #[diagnostic()] InvalidPolicyGlobPattern { /// The glob pattern that caused the error. pattern: String, @@ -42,6 +46,7 @@ pub enum Error { /// An invalid data. #[error("Invalid data, error: {error})")] + #[diagnostic()] InvalidData { /// The error that occurred. error: String, @@ -49,6 +54,7 @@ pub enum Error { /// An invalid input. #[error("Invalid input, error: {error})")] + #[diagnostic()] InvalidInput { /// The error that occurred. error: String, @@ -56,6 +62,7 @@ pub enum Error { /// Violation evaluation error. #[error("Violation evaluation error: {error}")] + #[diagnostic()] ViolationEvaluationError { /// The error that occurred. error: String, @@ -63,6 +70,7 @@ pub enum Error { /// A policy violation error. #[error("Policy violation: {violation}, provenance: {provenance}")] + #[diagnostic()] PolicyViolation { /// The provenance of the violation (URL or path). provenance: String, @@ -72,6 +80,7 @@ pub enum Error { /// A container for multiple errors. #[error("{:?}", format_errors(.0))] + #[diagnostic()] CompoundError(Vec), } diff --git a/crates/weaver_common/Cargo.toml b/crates/weaver_common/Cargo.toml index 43e3e805..5f5c6f4c 100644 --- a/crates/weaver_common/Cargo.toml +++ b/crates/weaver_common/Cargo.toml @@ -13,7 +13,9 @@ workspace = true [dependencies] paris = { version = "1.5.15", features = ["macros"] } -miette = { version = "7.2.0", features = ["fancy"] } +serde.workspace = true +serde_json.workspace = true +miette.workspace = true [dev-dependencies] thiserror.workspace = true diff --git a/crates/weaver_common/src/diag/mod.rs b/crates/weaver_common/src/diag/mod.rs index a86dcc2a..aa82aaac 100644 --- a/crates/weaver_common/src/diag/mod.rs +++ b/crates/weaver_common/src/diag/mod.rs @@ -146,6 +146,11 @@ mod tests { #[label("This bit here")] bad_bit: SourceSpan, }, + #[error("Compound errors")] + CompoundError { + #[related] + errors: Vec, + } } #[test] diff --git a/crates/weaver_common/src/diagnostic.rs b/crates/weaver_common/src/diagnostic.rs new file mode 100644 index 00000000..d525c0ac --- /dev/null +++ b/crates/weaver_common/src/diagnostic.rs @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! A generic diagnostic message + +use std::error::Error; +use miette::{Diagnostic, LabeledSpan, Report, Severity}; +use serde::Serialize; +use crate::error::WeaverDiagnostic; + +/// An extension to the [`miette::Diagnostic`] struct that adds an ansi message +/// representation of the diagnostic message. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct MietteDiagnosticExt { + /// Displayed diagnostic message + pub message: String, + /// Displayed diagnostic message with ansi color codes + pub ansi_message: String, + /// Unique diagnostic code to look up more information + /// about this Diagnostic. Ideally also globally unique, and documented + /// in the toplevel crate's documentation for easy searching. + /// Rust path format (`foo::bar::baz`) is recommended, but more classic + /// codes like `E0123` will work just fine + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + /// [`Diagnostic`] severity. Intended to be used by + /// [`ReportHandler`](crate::ReportHandler)s to change the way different + /// [`Diagnostic`]s are displayed. Defaults to [`Severity::Error`] + #[serde(skip_serializing_if = "Option::is_none")] + pub severity: Option, + /// Additional help text related to this Diagnostic + #[serde(skip_serializing_if = "Option::is_none")] + pub help: Option, + /// URL to visit for a more detailed explanation/help about this + /// [`Diagnostic`]. + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, + /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`] + #[serde(skip_serializing_if = "Option::is_none")] + pub labels: Option>, +} + +/// A generic and serializable representation of a diagnostic message +#[derive(Debug, serde::Serialize)] +pub struct DiagnosticMessage { + /// The error + error: serde_json::Value, + /// The diagnostic message + diagnostic: MietteDiagnosticExt, +} + +/// A list of diagnostic messages +#[derive(Debug, serde::Serialize)] +#[serde(transparent)] +pub struct DiagnosticMessages(Vec); + +impl DiagnosticMessage { + /// Creates a new diagnostic message from an error + pub fn new(error: M) -> Self { + let json_error = serde_json::to_value(&error).expect("Failed to serialize error"); + let message = error.to_string(); + let code = error.code().map(|error_code| error_code.to_string()); + let severity = error.severity(); + let help = error.help().map(|help| help.to_string()); + let url = error.url().map(|url| url.to_string()); + let labels = error.labels().map(|labels| labels.collect()); + + let diagnostic = MietteDiagnosticExt { + message, + ansi_message: format!("{:?}", Report::new(error)), + code, + severity, + help, + url, + labels, + }; + Self { + error: json_error, + diagnostic, + } + } +} + +impl DiagnosticMessages { + /// Creates a new list of diagnostic messages for a list of errors + pub fn from_errors(errors: Vec) -> Self { + Self(errors.into_iter().map(DiagnosticMessage::new).collect()) + } + + /// Creates a new list of diagnostic messages from a single error + pub fn from_error(error: M) -> Self { + Self(vec![DiagnosticMessage::new(error)]) + } +} + +impl From for DiagnosticMessages { + /// Convert errors marked with the DiagnosticMessage trait into a DiagnosticMessages. + fn from(error: T) -> Self { + Self(vec![DiagnosticMessage::new(error)]) + } +} diff --git a/crates/weaver_common/src/error.rs b/crates/weaver_common/src/error.rs index 0c99a647..b0777432 100644 --- a/crates/weaver_common/src/error.rs +++ b/crates/weaver_common/src/error.rs @@ -6,9 +6,24 @@ use crate::Logger; use std::process::exit; +use miette::Diagnostic; +use serde::Serialize; + +/// A trait marker for Weaver diagnostic. +pub trait WeaverDiagnostic {} + +/// A blanket implementation of the `WeaverDiagnostic` trait for any type that +/// implements the `Diagnostic` and `Serialize` traits. +/// +/// This allows any type that implements `Diagnostic` and `Serialize` to be +/// converted into [crate::diagnostic::DiagnosticMessages]. +impl WeaverDiagnostic for T + where + T: Serialize + Diagnostic + Send + Sync + ?Sized, +{ } /// A trait for custom error handling in the `weaver` crates. -pub trait WeaverError { +pub trait WeaverError : Serialize + Diagnostic + Send + Sync { /// Retrieves a list of error messages associated with this error. /// For compound errors, this method should return a list of all /// error messages. For simple errors, this method should return diff --git a/crates/weaver_common/src/lib.rs b/crates/weaver_common/src/lib.rs index 1f6c1581..c49ae2b3 100644 --- a/crates/weaver_common/src/lib.rs +++ b/crates/weaver_common/src/lib.rs @@ -6,6 +6,7 @@ pub mod diag; pub mod error; pub mod in_memory; pub mod quiet; +pub mod diagnostic; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, Mutex}; diff --git a/crates/weaver_forge/Cargo.toml b/crates/weaver_forge/Cargo.toml index 8cbbdd15..05aa2981 100644 --- a/crates/weaver_forge/Cargo.toml +++ b/crates/weaver_forge/Cargo.toml @@ -35,6 +35,7 @@ serde_json.workspace = true rayon.workspace = true walkdir.workspace = true globset.workspace = true +miette.workspace = true [dev-dependencies] opentelemetry = { version = "0.22.0", features = ["trace", "metrics", "logs", "otel_unstable"] } diff --git a/crates/weaver_forge/src/error.rs b/crates/weaver_forge/src/error.rs index 51a0ccf0..7b86ce99 100644 --- a/crates/weaver_forge/src/error.rs +++ b/crates/weaver_forge/src/error.rs @@ -4,11 +4,13 @@ use crate::error::Error::CompoundError; use std::{path::PathBuf, str::FromStr}; +use miette::Diagnostic; +use serde::Serialize; use weaver_common::error::WeaverError; use weaver_resolved_schema::attribute::AttributeRef; /// Errors emitted by this crate. -#[derive(thiserror::Error, Debug, Clone)] +#[derive(thiserror::Error, Debug, Clone, Diagnostic, Serialize)] #[non_exhaustive] pub enum Error { /// Invalid config file. diff --git a/crates/weaver_forge/src/extensions/ansi_code.rs b/crates/weaver_forge/src/extensions/ansi_code.rs new file mode 100644 index 00000000..673bd7f7 --- /dev/null +++ b/crates/weaver_forge/src/extensions/ansi_code.rs @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Set of filters used to add style and color to the console. + +use minijinja::Value; + +/// Converts the input value into a text with a black foreground color. +#[must_use] +pub(crate) fn black(input: &Value) -> String { + format!("\x1b[30m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a red foreground color. +#[must_use] +pub(crate) fn red(input: &Value) -> String { + format!("\x1b[31m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a green foreground color. +#[must_use] +pub(crate) fn green(input: &Value) -> String { + format!("\x1b[32m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a yellow foreground color. +#[must_use] +pub(crate) fn yellow(input: &Value) -> String { + format!("\x1b[33m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a blue foreground color. +#[must_use] +pub(crate) fn blue(input: &Value) -> String { + format!("\x1b[34m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a magenta foreground color. +#[must_use] +pub(crate) fn magenta(input: &Value) -> String { + format!("\x1b[35m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a cyan foreground color. +#[must_use] +pub(crate) fn cyan(input: &Value) -> String { + format!("\x1b[36m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a white foreground color. +#[must_use] +pub(crate) fn white(input: &Value) -> String { + format!("\x1b[37m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright black foreground color. +#[must_use] +pub(crate) fn bright_black(input: &Value) -> String { + format!("\x1b[90m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright red foreground color. +#[must_use] +pub(crate) fn bright_red(input: &Value) -> String { + format!("\x1b[91m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright green foreground color. +#[must_use] +pub(crate) fn bright_green(input: &Value) -> String { + format!("\x1b[92m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright yellow foreground color. +#[must_use] +pub(crate) fn bright_yellow(input: &Value) -> String { + format!("\x1b[93m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright blue foreground color. +#[must_use] +pub(crate) fn bright_blue(input: &Value) -> String { + format!("\x1b[94m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright magenta foreground color. +#[must_use] +pub(crate) fn bright_magenta(input: &Value) -> String { + format!("\x1b[95m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright cyan foreground color. +#[must_use] +pub(crate) fn bright_cyan(input: &Value) -> String { + format!("\x1b[96m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright white foreground color. +#[must_use] +pub(crate) fn bright_white(input: &Value) -> String { + format!("\x1b[97m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a black background color. +#[must_use] +pub(crate) fn bg_black(input: &Value) -> String { + format!("\x1b[40m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a red background color. +#[must_use] +pub(crate) fn bg_red(input: &Value) -> String { + format!("\x1b[41m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a green background color. +#[must_use] +pub(crate) fn bg_green(input: &Value) -> String { + format!("\x1b[42m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a yellow background color. +#[must_use] +pub(crate) fn bg_yellow(input: &Value) -> String { + format!("\x1b[43m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a blue background color. +#[must_use] +pub(crate) fn bg_blue(input: &Value) -> String { + format!("\x1b[44m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a magenta background color. +#[must_use] +pub(crate) fn bg_magenta(input: &Value) -> String { + format!("\x1b[45m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a cyan background color. +#[must_use] +pub(crate) fn bg_cyan(input: &Value) -> String { + format!("\x1b[46m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a white background color. +#[must_use] +pub(crate) fn bg_white(input: &Value) -> String { + format!("\x1b[47m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright black background color. +#[must_use] +pub(crate) fn bg_bright_black(input: &Value) -> String { + format!("\x1b[100m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright red background color. +#[must_use] +pub(crate) fn bg_bright_red(input: &Value) -> String { + format!("\x1b[101m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright green background color. +#[must_use] +pub(crate) fn bg_bright_green(input: &Value) -> String { + format!("\x1b[102m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright yellow background color. +#[must_use] +pub(crate) fn bg_bright_yellow(input: &Value) -> String { + format!("\x1b[103m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright blue background color. +#[must_use] +pub(crate) fn bg_bright_blue(input: &Value) -> String { + format!("\x1b[104m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright magenta background color. +#[must_use] +pub(crate) fn bg_bright_magenta(input: &Value) -> String { + format!("\x1b[105m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright cyan background color. +#[must_use] +pub(crate) fn bg_bright_cyan(input: &Value) -> String { + format!("\x1b[106m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bright white background color. +#[must_use] +pub(crate) fn bg_bright_white(input: &Value) -> String { + format!("\x1b[107m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a bold style. +#[must_use] +pub(crate) fn bold(input: &Value) -> String { + format!("\x1b[1m{}\x1b[0m", input) +} + +/// Converts the input value into a text with an italic style. +#[must_use] +pub(crate) fn italic(input: &Value) -> String { + format!("\x1b[3m{}\x1b[0m", input) +} + +/// Converts the input value into a text with an underline style. +#[must_use] +pub(crate) fn underline(input: &Value) -> String { + format!("\x1b[4m{}\x1b[0m", input) +} + +/// Converts the input value into a text with a strikethrough style. +#[must_use] +pub(crate) fn strikethrough(input: &Value) -> String { + format!("\x1b[9m{}\x1b[0m", input) +} \ No newline at end of file diff --git a/crates/weaver_forge/src/extensions/mod.rs b/crates/weaver_forge/src/extensions/mod.rs index 40e53026..90651ffc 100644 --- a/crates/weaver_forge/src/extensions/mod.rs +++ b/crates/weaver_forge/src/extensions/mod.rs @@ -5,3 +5,4 @@ pub mod acronym; pub mod case_converter; pub mod code; pub mod otel; +pub mod ansi_code; diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index f8502460..a3e96975 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -2,15 +2,15 @@ #![doc = include_str!("../README.md")] +use std::{fs, io}; use std::borrow::Cow; use std::fmt::{Debug, Display, Formatter}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; -use std::{fs, io}; +use minijinja::{Environment, ErrorKind, State, Value}; use minijinja::syntax::SyntaxConfig; use minijinja::value::{from_args, Object}; -use minijinja::{Environment, ErrorKind, State, Value}; use rayon::iter::IntoParallelIterator; use rayon::iter::ParallelIterator; use serde::Serialize; @@ -65,10 +65,22 @@ impl GeneratorConfig { } } +/// The type of output where the generated content will be written. +#[derive(Debug, Clone)] +pub enum OutputType { + /// Write the generated content to the standard output. + Stdout, + /// Write the generated content to the standard error. + Stderr, + /// Write the generated content to a file. + File, +} + /// A template object accessible from the template. #[derive(Debug, Clone)] struct TemplateObject { file_name: Arc>, + output_type: Arc>, } impl TemplateObject { @@ -95,9 +107,24 @@ impl Object for TemplateObject { args: &[Value], ) -> Result { if name == "set_file_name" { - let (file_name,): (&str,) = from_args(args)?; + let (file_name, ): (&str, ) = from_args(args)?; file_name.clone_into(&mut self.file_name.lock().expect("Lock poisoned")); Ok(Value::from("")) + } else if name == "set_output_type" { + let (output_type, ): (&str, ) = from_args(args)?; + let output_type = match output_type { + "stdout" => OutputType::Stdout, + "stderr" => OutputType::Stderr, + "file" => OutputType::File, + _ => { + return Err(minijinja::Error::new( + ErrorKind::UnknownMethod, + format!("unknown output type: {output_type}"), + )); + } + }; + output_type.clone_into(&mut self.output_type.lock().expect("Lock poisoned")); + Ok(Value::from("")) } else { Err(minijinja::Error::new( ErrorKind::UnknownMethod, @@ -268,8 +295,8 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, output_dir, ) { @@ -303,8 +330,8 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, output_dir, ) { @@ -331,6 +358,7 @@ impl TemplateEngine { file_name: Arc::new(Mutex::new( template_path.to_str().unwrap_or_default().to_owned(), )), + output_type: Arc::new(Mutex::new(OutputType::File)), }; let mut engine = self.template_engine()?; let template_file = template_path.to_str().ok_or(InvalidTemplateFile { @@ -361,9 +389,19 @@ impl TemplateEngine { error_id: e.to_string(), error: error_summary(e), })?; - let generated_file = - Self::save_generated_code(output_dir, template_object.file_name(), output)?; - log.success(&format!("Generated file {:?}", generated_file)); + match template_object.output_type.lock().expect("Lock poisoned").clone() { + OutputType::Stdout => { + println!("{}", output); + } + OutputType::Stderr => { + eprintln!("{}", output); + } + OutputType::File => { + let generated_file = + Self::save_generated_code(output_dir, template_object.file_name(), output)?; + log.success(&format!("Generated file {:?}", generated_file)); + } + } Ok(()) } @@ -461,6 +499,48 @@ impl TemplateEngine { env.add_filter("metric_namespace", extensions::otel::metric_namespace); env.add_filter("required", extensions::otel::required); env.add_filter("not_required", extensions::otel::not_required); + + env.add_filter("black", extensions::ansi_code::black); + env.add_filter("red", extensions::ansi_code::red); + env.add_filter("green", extensions::ansi_code::green); + env.add_filter("yellow", extensions::ansi_code::yellow); + env.add_filter("blue", extensions::ansi_code::blue); + env.add_filter("magenta", extensions::ansi_code::magenta); + env.add_filter("cyan", extensions::ansi_code::cyan); + env.add_filter("white", extensions::ansi_code::white); + + env.add_filter("bright_black", extensions::ansi_code::bright_black); + env.add_filter("bright_red", extensions::ansi_code::bright_red); + env.add_filter("bright_green", extensions::ansi_code::bright_green); + env.add_filter("bright_yellow", extensions::ansi_code::bright_yellow); + env.add_filter("bright_blue", extensions::ansi_code::bright_blue); + env.add_filter("bright_magenta", extensions::ansi_code::bright_magenta); + env.add_filter("bright_cyan", extensions::ansi_code::bright_cyan); + env.add_filter("bright_white", extensions::ansi_code::bright_white); + + env.add_filter("bg_black", extensions::ansi_code::bg_black); + env.add_filter("bg_red", extensions::ansi_code::bg_red); + env.add_filter("bg_green", extensions::ansi_code::bg_green); + env.add_filter("bg_yellow", extensions::ansi_code::bg_yellow); + env.add_filter("bg_blue", extensions::ansi_code::bg_blue); + env.add_filter("bg_magenta", extensions::ansi_code::bg_magenta); + env.add_filter("bg_cyan", extensions::ansi_code::bg_cyan); + env.add_filter("bg_white", extensions::ansi_code::bg_white); + + env.add_filter("bg_bright_black", extensions::ansi_code::bg_bright_black); + env.add_filter("bg_bright_red", extensions::ansi_code::bg_bright_red); + env.add_filter("bg_bright_green", extensions::ansi_code::bg_bright_green); + env.add_filter("bg_bright_yellow", extensions::ansi_code::bg_bright_yellow); + env.add_filter("bg_bright_blue", extensions::ansi_code::bg_bright_blue); + env.add_filter("bg_bright_magenta", extensions::ansi_code::bg_bright_magenta); + env.add_filter("bg_bright_cyan", extensions::ansi_code::bg_bright_cyan); + env.add_filter("bg_bright_white", extensions::ansi_code::bg_bright_white); + + env.add_filter("bold", extensions::ansi_code::bold); + env.add_filter("italic", extensions::ansi_code::italic); + env.add_filter("underline", extensions::ansi_code::underline); + env.add_filter("strikethrough", extensions::ansi_code::strikethrough); + // ToDo Implement more filters: stable, experimental, deprecated env.add_test("stable", extensions::otel::is_stable); env.add_test("experimental", extensions::otel::is_experimental); @@ -521,7 +601,7 @@ fn cross_platform_loader<'x, P: AsRef + 'x>( ErrorKind::InvalidOperation, "could not read template", ) - .with_source(err)), + .with_source(err)), } } } @@ -755,12 +835,12 @@ mod tests { schema.registry(registry_id).expect("registry not found"), schema.catalog(), ) - .unwrap_or_else(|e| { - panic!( - "Failed to create the context for the template evaluation: {:?}", - e - ) - }); + .unwrap_or_else(|e| { + panic!( + "Failed to create the context for the template evaluation: {:?}", + e + ) + }); engine .generate( diff --git a/crates/weaver_resolver/Cargo.toml b/crates/weaver_resolver/Cargo.toml index d6831f55..1d19186f 100644 --- a/crates/weaver_resolver/Cargo.toml +++ b/crates/weaver_resolver/Cargo.toml @@ -23,6 +23,7 @@ rayon.workspace = true serde.workspace = true serde_json.workspace = true walkdir.workspace = true +miette.workspace = true [dev-dependencies] glob = "0.3.1" diff --git a/crates/weaver_resolver/src/lib.rs b/crates/weaver_resolver/src/lib.rs index af5aee9d..2b09fb09 100644 --- a/crates/weaver_resolver/src/lib.rs +++ b/crates/weaver_resolver/src/lib.rs @@ -4,9 +4,11 @@ use std::collections::HashMap; use std::path::{MAIN_SEPARATOR, PathBuf}; +use miette::Diagnostic; use rayon::iter::ParallelBridge; use rayon::iter::ParallelIterator; +use serde::Serialize; use walkdir::DirEntry; use weaver_cache::Cache; @@ -31,7 +33,7 @@ pub mod registry; pub struct SchemaResolver {} /// An error that can occur while resolving a telemetry schema. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Serialize, Diagnostic)] #[must_use] #[non_exhaustive] pub enum Error { @@ -46,6 +48,7 @@ pub enum Error { /// A semantic convention error. #[error("{message}")] + #[diagnostic()] SemConvError { /// The error that occurred. message: String, diff --git a/crates/weaver_semconv/Cargo.toml b/crates/weaver_semconv/Cargo.toml index 27a4f267..70c0c7c0 100644 --- a/crates/weaver_semconv/Cargo.toml +++ b/crates/weaver_semconv/Cargo.toml @@ -19,5 +19,6 @@ serde_yaml.workspace = true thiserror.workspace = true ureq.workspace = true ordered-float.workspace = true +miette.workspace = true glob = "0.3.1" \ No newline at end of file diff --git a/crates/weaver_semconv/src/lib.rs b/crates/weaver_semconv/src/lib.rs index 8d6c3815..b549c758 100644 --- a/crates/weaver_semconv/src/lib.rs +++ b/crates/weaver_semconv/src/lib.rs @@ -2,6 +2,8 @@ #![doc = include_str!("../README.md")] +use miette::Diagnostic; +use serde::Serialize; use weaver_common::error::{format_errors, WeaverError}; pub mod attribute; @@ -14,7 +16,7 @@ pub mod stability; pub mod stats; /// An error that can occur while loading a semantic convention registry. -#[derive(thiserror::Error, Debug, Clone, PartialEq)] +#[derive(thiserror::Error, Debug, Clone, PartialEq, Serialize, Diagnostic)] #[non_exhaustive] pub enum Error { /// The semantic convention registry path pattern is invalid. diff --git a/crates/weaver_semconv_gen/Cargo.toml b/crates/weaver_semconv_gen/Cargo.toml index a70b39d4..3263dc16 100644 --- a/crates/weaver_semconv_gen/Cargo.toml +++ b/crates/weaver_semconv_gen/Cargo.toml @@ -21,6 +21,7 @@ nom = "7.1.3" itertools.workspace = true thiserror.workspace = true serde.workspace = true +miette.workspace = true [lints] workspace = true diff --git a/crates/weaver_semconv_gen/src/gen.rs b/crates/weaver_semconv_gen/src/gen.rs index 80059926..de3136e7 100644 --- a/crates/weaver_semconv_gen/src/gen.rs +++ b/crates/weaver_semconv_gen/src/gen.rs @@ -36,7 +36,7 @@ impl GenerateMarkdownContext { /// Renders stored notes into markdown format. fn write_rendered_notes(&self, out: &mut Out) -> Result<(), Error> { for (counter, note) in self.notes.iter().enumerate() { - write!(out, "\n**[{}]:** {}\n", counter + 1, note.trim())?; + write!(out, "\n**[{}]:** {}\n", counter + 1, note.trim()).map_err(|e| Error::StdIoError(e.to_string())).map_err(|e| Error::StdIoError(e.to_string()))?; } Ok(()) } @@ -62,9 +62,9 @@ fn write_example_list( let mut first = true; for e in list { if !first { - write!(out, "; ")?; + write!(out, "; ").map_err(|e| Error::StdIoError(e.to_string()))?; } - write!(out, "`{e}`")?; + write!(out, "`{e}`").map_err(|e| Error::StdIoError(e.to_string()))?; first = false; } Ok(()) @@ -72,10 +72,10 @@ fn write_example_list( fn write_examples_string(out: &mut Out, examples: &Examples) -> Result<(), Error> { match examples { - Examples::Bool(value) => Ok(write!(out, "`{value}`")?), - Examples::Int(value) => Ok(write!(out, "`{value}`")?), - Examples::Double(value) => Ok(write!(out, "`{value}`")?), - Examples::String(value) => Ok(write!(out, "`{value}`")?), + Examples::Bool(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), + Examples::Int(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), + Examples::Double(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), + Examples::String(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), Examples::Ints(values) => write_example_list(out, values), Examples::Doubles(values) => write_example_list(out, values), Examples::Bools(values) => write_example_list(out, values), @@ -85,9 +85,9 @@ fn write_examples_string(out: &mut Out, examples: &Examples) -> Resu fn write_enum_value_string(out: &mut Out, value: &ValueSpec) -> Result<(), Error> { match value { - ValueSpec::Double(v) => write!(out, "`{v}`")?, - ValueSpec::Int(v) => write!(out, "`{v}`")?, - ValueSpec::String(v) => write!(out, "`{v}`")?, + ValueSpec::Double(v) => write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?, + ValueSpec::Int(v) => write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?, + ValueSpec::String(v) => write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?, } Ok(()) } @@ -99,7 +99,7 @@ fn write_enum_examples_string( let mut first = true; for entry in members.iter().take(3) { if !first { - write!(out, "; ")?; + write!(out, "; ").map_err(|e| Error::StdIoError(e.to_string()))?; } write_enum_value_string(out, &entry.value)?; first = false; @@ -115,15 +115,15 @@ fn write_stability_badge( Some(Stability::Stable) => write!( out, "![Stable](https://img.shields.io/badge/-stable-lightgreen)" - )?, + ).map_err(|e| Error::StdIoError(e.to_string()))?, Some(Stability::Deprecated) => write!( out, "![Deprecated](https://img.shields.io/badge/-deprecated-red)" - )?, + ).map_err(|e| Error::StdIoError(e.to_string()))?, Some(Stability::Experimental) | None => write!( out, "![Experimental](https://img.shields.io/badge/-experimental-blue)" - )?, + ).map_err(|e| Error::StdIoError(e.to_string()))?, } Ok(()) } @@ -136,8 +136,8 @@ struct AttributeView<'a> { impl<'a> AttributeView<'a> { fn write_name(&self, out: &mut T) -> Result<(), Error> { match &self.attribute.r#type { - AttributeType::Template(_) => Ok(write!(out, "{}.", self.attribute.name)?), - _ => Ok(write!(out, "{}", self.attribute.name)?), + AttributeType::Template(_) => Ok(write!(out, "{}.", self.attribute.name).map_err(|e| Error::StdIoError(e.to_string()))?), + _ => Ok(write!(out, "{}", self.attribute.name).map_err(|e| Error::StdIoError(e.to_string()))?), } } @@ -151,7 +151,7 @@ impl<'a> AttributeView<'a> { .replace('_', "-"); // TODO - We should try to link to the name itself, instead // of just the correct group. - Ok(write!(out, "{prefix}/{reg_name}.md")?) + Ok(write!(out, "{prefix}/{reg_name}.md").map_err(|e| Error::StdIoError(e.to_string()))?) } fn write_name_with_optional_link( @@ -161,11 +161,11 @@ impl<'a> AttributeView<'a> { ) -> Result<(), Error> { match attribute_registry_base_url { Some(prefix) => { - write!(out, "[`")?; + write!(out, "[`").map_err(|e| Error::StdIoError(e.to_string()))?; self.write_name(out)?; - write!(out, "`](")?; + write!(out, "`](").map_err(|e| Error::StdIoError(e.to_string()))?; self.write_registry_link(out, prefix)?; - write!(out, ")")?; + write!(out, ")").map_err(|e| Error::StdIoError(e.to_string()))?; } None => self.write_name(out)?, } @@ -189,27 +189,27 @@ impl<'a> AttributeView<'a> { write!( out, "\n| Value | Description | Stability |\n|---|---|---|\n" - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; if let AttributeType::Enum { members, .. } = &self.attribute.r#type { for m in members { - write!(out, "| ")?; + write!(out, "| ").map_err(|e| Error::StdIoError(e.to_string()))?; write_enum_value_string(out, &m.value)?; - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; if let Some(v) = m.brief.as_ref() { - write!(out, "{}", v.trim())?; + write!(out, "{}", v.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; } else { // Use the id as the description if missing a brief. - write!(out, "{}", m.id)?; + write!(out, "{}", m.id).map_err(|e| Error::StdIoError(e.to_string()))?; } // Stability. - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; if let Some(note) = m.deprecated.as_ref() { write_stability_badge(out, &Some(Stability::Deprecated))?; - write!(out, "
{}", note.trim())?; + write!(out, "
{}", note.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; } else { write_stability_badge(out, &m.stability)?; } - writeln!(out, " |")?; + writeln!(out, " |").map_err(|e| Error::StdIoError(e.to_string()))?; } } // TODO - error message on not enum?... @@ -239,7 +239,7 @@ impl<'a> AttributeView<'a> { } fn write_type_string(&self, out: &mut Out) -> Result<(), Error> { - write!(out, "{}", self.type_string())?; + write!(out, "{}", self.type_string()).map_err(|e| Error::StdIoError(e.to_string()))?; Ok(()) } @@ -249,7 +249,7 @@ impl<'a> AttributeView<'a> { ctx: &mut GenerateMarkdownContext, ) -> Result<(), Error> { if self.attribute.note.is_empty() { - write!(out, "{}", self.attribute.brief.trim())?; + write!(out, "{}", self.attribute.brief.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; Ok(()) } else { write!( @@ -257,7 +257,7 @@ impl<'a> AttributeView<'a> { "{} {}", self.attribute.brief.trim(), ctx.add_note(self.attribute.note.clone()) - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; Ok(()) } } @@ -269,13 +269,13 @@ impl<'a> AttributeView<'a> { ) -> Result<(), Error> { match &self.attribute.requirement_level { RequirementLevel::Basic(BasicRequirementLevelSpec::Required) => { - Ok(write!(out, "`Required`")?) + Ok(write!(out, "`Required`").map_err(|e| Error::StdIoError(e.to_string()))?) } RequirementLevel::Basic(BasicRequirementLevelSpec::Recommended) => { - Ok(write!(out, "`Recommended`")?) + Ok(write!(out, "`Recommended`").map_err(|e| Error::StdIoError(e.to_string()))?) } RequirementLevel::Basic(BasicRequirementLevelSpec::OptIn) => { - Ok(write!(out, "`Opt-In`")?) + Ok(write!(out, "`Opt-In`").map_err(|e| Error::StdIoError(e.to_string()))?) } RequirementLevel::ConditionallyRequired { text } => { if text.len() > BREAK_COUNT { @@ -283,16 +283,16 @@ impl<'a> AttributeView<'a> { out, "`Conditionally Required` {}", ctx.add_note(text.clone()) - )?) + ).map_err(|e| Error::StdIoError(e.to_string()))?) } else { - Ok(write!(out, "`Conditionally Required` {text}")?) + Ok(write!(out, "`Conditionally Required` {text}").map_err(|e| Error::StdIoError(e.to_string()))?) } } RequirementLevel::Recommended { text } => { if text.len() > BREAK_COUNT { - Ok(write!(out, "`Recommended` {}", ctx.add_note(text.clone()))?) + Ok(write!(out, "`Recommended` {}", ctx.add_note(text.clone())).map_err(|e| Error::StdIoError(e.to_string()))?) } else { - Ok(write!(out, "`Recommended` {text}")?) + Ok(write!(out, "`Recommended` {text}").map_err(|e| Error::StdIoError(e.to_string()))?) } } } @@ -315,7 +315,7 @@ impl<'a> AttributeView<'a> { fn write_stability(&self, out: &mut Out) -> Result<(), Error> { if let Some(note) = self.attribute.deprecated.as_ref() { write_stability_badge(out, &Some(Stability::Deprecated))?; - write!(out, "
{}", note.trim())?; + write!(out, "
{}", note.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; } else { write_stability_badge(out, &self.attribute.stability)?; } @@ -406,38 +406,38 @@ impl<'a> AttributeTableView<'a> { } if self.group.r#type == GroupType::Event { - write!(out, "The event name MUST be `{}`\n\n", self.event_name())?; + write!(out, "The event name MUST be `{}`\n\n", self.event_name()).map_err(|e| Error::StdIoError(e.to_string()))?; } if args.is_omit_requirement() { writeln!( out, "| Attribute | Type | Description | Examples | Stability |" - )?; - writeln!(out, "|---|---|---|---|---|")?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, "|---|---|---|---|---|").map_err(|e| Error::StdIoError(e.to_string())).map_err(|e| Error::StdIoError(e.to_string()))?; } else { - writeln!(out, "| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |")?; - writeln!(out, "|---|---|---|---|---|---|")?; + writeln!(out, "| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |").map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, "|---|---|---|---|---|---|").map_err(|e| Error::StdIoError(e.to_string()))?; } for attr in &attributes { - write!(out, "| ")?; + write!(out, "| ").map_err(|e| Error::StdIoError(e.to_string()))?; attr.write_name_with_optional_link(out, attribute_registry_base_url)?; - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; attr.write_type_string(out)?; - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; attr.write_description(out, ctx)?; - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; attr.write_examples(out)?; if args.is_omit_requirement() { - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; } else { - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; attr.write_requirement(out, ctx)?; - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; } attr.write_stability(out)?; - writeln!(out, " |")?; + writeln!(out, " |").map_err(|e| Error::StdIoError(e.to_string()))?; } // Add "note" footers ctx.write_rendered_notes(out)?; @@ -453,23 +453,23 @@ impl<'a> AttributeTableView<'a> { write!( out, "\nThe following attributes can be important for making sampling decisions " - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; write!( out, "and SHOULD be provided **at span creation time** (if provided at all):\n\n" - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; for a in sampling_relevant { - write!(out, "* ")?; + write!(out, "* ").map_err(|e| Error::StdIoError(e.to_string()))?; a.write_name_with_optional_link(out, attribute_registry_base_url)?; - writeln!(out)?; + writeln!(out).map_err(|e| Error::StdIoError(e.to_string()))?; } } // Add enum footers for e in attributes.iter().filter(|a| a.is_enum()) { - write!(out, "\n`")?; + write!(out, "\n`").map_err(|e| Error::StdIoError(e.to_string()))?; e.write_name(out)?; - writeln!(out, "` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used.")?; + writeln!(out, "` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used.").map_err(|e| Error::StdIoError(e.to_string()))?; e.write_enum_spec_table(out)?; } Ok(()) @@ -510,8 +510,8 @@ impl<'a> MetricView<'a> { } fn write_unit(&self, out: &mut Out) -> Result<(), Error> { match self.group.unit.as_ref() { - Some(value) => write!(out, "{value}")?, - None => write!(out, "1")?, + Some(value) => write!(out, "{value}").map_err(|e| Error::StdIoError(e.to_string()))?, + None => write!(out, "1").map_err(|e| Error::StdIoError(e.to_string()))?, } Ok(()) } @@ -522,21 +522,21 @@ impl<'a> MetricView<'a> { ) -> Result<(), Error> { // TODO - add note if needed. if self.group.note.is_empty() { - write!(out, "{}", &self.group.brief)?; + write!(out, "{}", &self.group.brief).map_err(|e| Error::StdIoError(e.to_string()))?; } else { write!( out, "{} {}", &self.group.brief, ctx.add_note(self.group.note.clone()) - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; } Ok(()) } fn write_stability(&self, out: &mut Out) -> Result<(), Error> { if let Some(note) = self.group.deprecated.as_ref() { write_stability_badge(out, &Some(Stability::Deprecated))?; - write!(out, "
{}", note.trim())?; + write!(out, "
{}", note.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; } else { write_stability_badge(out, &self.group.stability)?; } @@ -550,23 +550,23 @@ impl<'a> MetricView<'a> { writeln!( out, "| Name | Instrument Type | Unit (UCUM) | Description | Stability |" - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; writeln!( out, "| -------- | --------------- | ----------- | -------------- | --------- |" - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; write!( out, "| `{}` | {} | `", self.metric_name(), self.instrument() - )?; + ).map_err(|e| Error::StdIoError(e.to_string()))?; self.write_unit(out)?; - write!(out, "` | ")?; + write!(out, "` | ").map_err(|e| Error::StdIoError(e.to_string()))?; self.write_description(out, ctx)?; - write!(out, " | ")?; + write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; self.write_stability(out)?; - writeln!(out, " |")?; + writeln!(out, " |").map_err(|e| Error::StdIoError(e.to_string()))?; // Add "note" footers ctx.write_rendered_notes(out)?; Ok(()) diff --git a/crates/weaver_semconv_gen/src/lib.rs b/crates/weaver_semconv_gen/src/lib.rs index 8cd3c266..07a02105 100644 --- a/crates/weaver_semconv_gen/src/lib.rs +++ b/crates/weaver_semconv_gen/src/lib.rs @@ -5,6 +5,7 @@ //! poorly porting the code into RUST. We expect to optimise and improve things over time. use std::fs; +use miette::Diagnostic; use serde::Serialize; use weaver_cache::Cache; @@ -26,7 +27,7 @@ mod gen; mod parser; /// Errors emitted by this crate. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Serialize, Diagnostic)] #[non_exhaustive] pub enum Error { /// Thrown when we are unable to find a semconv by id. @@ -66,12 +67,15 @@ pub enum Error { header: String, }, /// Errors from using std io library. - #[error(transparent)] - StdIoError(#[from] std::io::Error), + #[error("{0}")] + StdIoError(String), /// Errors from using std fmt library. - #[error(transparent)] - StdFmtError(#[from] std::fmt::Error), + #[error("{error}")] + StdFmtError { + /// The error message. + error: String, + }, /// Errors from using weaver_semconv. #[error(transparent)] @@ -238,11 +242,11 @@ pub fn update_markdown( dry_run: bool, attribute_registry_base_url: Option<&str>, ) -> Result<(), Error> { - let original_markdown = fs::read_to_string(file)?.replace("\r\n", "\n"); + let original_markdown = fs::read_to_string(file).map_err(|e| Error::StdIoError(e.to_string()))?.replace("\r\n", "\n"); let updated_markdown = update_markdown_contents(&original_markdown, generator, attribute_registry_base_url)?; if !dry_run { - fs::write(file, updated_markdown)?; + fs::write(file, updated_markdown).map_err(|e| Error::StdIoError(e.to_string()))?; Ok(()) } else if original_markdown != updated_markdown { Err(Error::MarkdownIsNotEqual { diff --git a/src/main.rs b/src/main.rs index 7938260b..586036f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,23 +18,25 @@ fn main() { let cli = Cli::parse(); let start = std::time::Instant::now(); - if cli.quiet { + let error_code = if cli.quiet { let log = QuietLogger::new(); - run_command(&cli, log); + run_command(&cli, log) } else { let log = ConsoleLogger::new(cli.debug); - run_command(&cli, log); + run_command(&cli, log) }; + let elapsed = start.elapsed(); println!("Total execution time: {:?}s", elapsed.as_secs_f64()); + std::process::exit(error_code); } #[cfg(not(tarpaulin_include))] -fn run_command(cli: &Cli, log: impl Logger + Sync + Clone) { +fn run_command(cli: &Cli, log: impl Logger + Sync + Clone) -> i32 { match &cli.command { Some(Commands::Registry(params)) => { - semconv_registry(log, params); + semconv_registry(log, params) } - None => {} + None => { 0 } } -} +} \ No newline at end of file diff --git a/src/registry/check.rs b/src/registry/check.rs index 45014bb0..c5cc30e7 100644 --- a/src/registry/check.rs +++ b/src/registry/check.rs @@ -6,12 +6,27 @@ use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, use clap::Args; use std::path::PathBuf; use weaver_cache::Cache; +use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::Logger; use weaver_semconv::registry::SemConvRegistry; /// Parameters for the `registry check` sub-command #[derive(Debug, Args)] pub struct RegistryCheckArgs { + /// Target to generate the artifacts for. + #[arg(default_value = "console")] + pub target: String, + + /// Path to the directory where the generated artifacts will be saved. + /// Default is the `output` directory. + #[arg(default_value = "output")] + pub output: PathBuf, + + /// Path to the directory where the templates are located. + /// Default is the `templates` directory. + #[arg(short = 't', long, default_value = "templates")] + pub templates: PathBuf, + /// Local path or Git URL of the semantic convention registry to check. #[arg( short = 'r', @@ -33,7 +48,7 @@ pub struct RegistryCheckArgs { /// Check a semantic convention registry. #[cfg(not(tarpaulin_include))] -pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: &RegistryCheckArgs) { +pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: &RegistryCheckArgs) -> Result<(), DiagnosticMessages> { logger.loading(&format!("Checking registry `{}`", args.registry)); let registry_id = "default"; @@ -48,7 +63,7 @@ pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: & ®istry_path, cache, logger.clone(), - ); + )?; check_policies( ®istry_path, @@ -56,8 +71,10 @@ pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: & &args.policies, &semconv_specs, logger.clone(), - ); + )?; let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); - _ = resolve_semconv_specs(&mut registry, logger); + _ = resolve_semconv_specs(&mut registry, logger.clone()); + + Ok(()) } diff --git a/src/registry/generate.rs b/src/registry/generate.rs index 7b707c5e..c5035807 100644 --- a/src/registry/generate.rs +++ b/src/registry/generate.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use clap::Args; use weaver_cache::Cache; -use weaver_common::error::ExitIfError; +use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; use weaver_forge::{GeneratorConfig, TemplateEngine}; @@ -56,7 +56,7 @@ pub(crate) fn command( logger: impl Logger + Sync + Clone, cache: &Cache, args: &RegistryGenerateArgs, -) { +) -> Result<(), DiagnosticMessages> { logger.loading(&format!( "Generating artifacts for the registry `{}`", args.registry @@ -73,32 +73,31 @@ pub(crate) fn command( ®istry_path, cache, logger.clone(), - ); + )?; + check_policies( ®istry_path, cache, &args.before_resolution_policies, &semconv_specs, logger.clone(), - ); + )?; let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); let schema = resolve_semconv_specs(&mut registry, logger.clone()); let config = GeneratorConfig::new(args.templates.clone()); - let engine = TemplateEngine::try_new(&format!("registry/{}", args.target), config) - .exit_if_error(logger.clone()); + let engine = TemplateEngine::try_new(&format!("registry/{}", args.target), config)?; let template_registry = TemplateRegistry::try_from_resolved_registry( schema .registry(registry_id) .expect("Failed to get the registry from the resolved schema"), schema.catalog(), - ) - .exit_if_error(logger.clone()); + )?; engine - .generate(logger.clone(), &template_registry, args.output.as_path()) - .exit_if_error(logger.clone()); + .generate(logger.clone(), &template_registry, args.output.as_path())?; logger.success("Artifacts generated successfully"); + Ok(()) } diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 07d58f42..f59be616 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::path::PathBuf; +use std::process::exit; use std::str::FromStr; use clap::{Args, Subcommand}; @@ -13,8 +14,10 @@ use check::RegistryCheckArgs; use weaver_cache::Cache; use weaver_checker::{Engine, Error, PolicyPackage}; use weaver_checker::Error::{InvalidPolicyFile, PolicyViolation}; +use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::error::{ExitIfError, handle_errors}; use weaver_common::Logger; +use weaver_forge::{GeneratorConfig, TemplateEngine}; use weaver_resolved_schema::ResolvedTelemetrySchema; use weaver_resolver::SchemaResolver; use weaver_semconv::registry::SemConvRegistry; @@ -112,22 +115,59 @@ pub struct RegistryArgs { /// Manage a semantic convention registry. #[cfg(not(tarpaulin_include))] -pub fn semconv_registry(log: impl Logger + Sync + Clone, command: &RegistryCommand) { +pub fn semconv_registry(log: impl Logger + Sync + Clone, command: &RegistryCommand) -> i32 { let cache = Cache::try_new().unwrap_or_else(|e| { log.error(&e.to_string()); #[allow(clippy::exit)] // Expected behavior - std::process::exit(1); + exit(1); }); match &command.command { - RegistrySubCommand::Check(args) => check::command(log, &cache, args), - RegistrySubCommand::Generate(args) => generate::command(log, &cache, args), - RegistrySubCommand::Stats(args) => stats::command(log, &cache, args), - RegistrySubCommand::Resolve(args) => resolve::command(log, &cache, args), + RegistrySubCommand::Check(args) => { + write_diagnostics( + check::command(log.clone(), &cache, args), + args.templates.clone(), args.output.clone(), args.target.clone(), log.clone()) + }, + RegistrySubCommand::Generate(args) => { + write_diagnostics( + generate::command(log.clone(), &cache, args), + args.templates.clone(), args.output.clone(), args.target.clone(), log.clone()) + } + RegistrySubCommand::Stats(args) => { + stats::command(log, &cache, args); + 0 + }, + RegistrySubCommand::Resolve(args) => { + resolve::command(log, &cache, args); + 0 + }, RegistrySubCommand::Search(_) => { unimplemented!() } - RegistrySubCommand::UpdateMarkdown(args) => update_markdown::command(log, &cache, args), + RegistrySubCommand::UpdateMarkdown(args) => { + update_markdown::command(log, &cache, args); + 0 + }, + } +} + +fn write_diagnostics( + result: Result<(),DiagnosticMessages>, + template_root: PathBuf, + output: PathBuf, + target: String,logger: impl Logger + Sync + Clone) -> i32 { + if let Err(e) = result { + let config = GeneratorConfig::new(template_root); + let engine = TemplateEngine::try_new(&format!("errors/{}", target), config) + .exit_if_error(logger.clone()); + + engine + .generate(logger.clone(), &e, output.as_path()) + .exit_if_error(logger.clone()); + + 1 + } else { + 0 } } @@ -160,14 +200,14 @@ pub(crate) fn load_semconv_specs( registry_path: &weaver_semconv::path::RegistryPath, cache: &Cache, log: impl Logger + Sync + Clone, -) -> Vec<(String, SemConvSpec)> { +) -> Result, weaver_resolver::Error> { let semconv_specs = - SchemaResolver::load_semconv_specs(®istry_path, cache).exit_if_error(log.clone()); + SchemaResolver::load_semconv_specs(®istry_path, cache)?; log.success(&format!( "SemConv registry loaded ({} files)", semconv_specs.len() )); - semconv_specs + Ok(semconv_specs) } /// Check the policies of a semantic convention registry. @@ -232,26 +272,31 @@ fn check_policies( policies: &[PathBuf], semconv_specs: &[(String, SemConvSpec)], logger: impl Logger + Sync + Clone, -) { +) -> Result<(), DiagnosticMessages> { let mut engine = Engine::new(); // Add policies from the registry - let (registry_path, _) = SchemaResolver::path_to_registry(registry_path, cache) - .exit_if_error(logger.clone()); - let added_policies_count = engine.add_policies(registry_path.as_path(), "*.rego") - .exit_if_error(logger.clone()); + let (registry_path, _) = SchemaResolver::path_to_registry(registry_path, cache)?; + let added_policies_count = engine.add_policies(registry_path.as_path(), "*.rego")?; // Add policies from the command line for policy in policies { - engine.add_policy(policy).exit_if_error(logger.clone()); + engine.add_policy(policy)?; } if added_policies_count + policies.len() > 0 { - check_policy(&engine, semconv_specs).exit_if_error(logger.clone()); + check_policy(&engine, semconv_specs).map_err(|e| { + if let Error::CompoundError(errors) = e { + DiagnosticMessages::from_errors(errors) + } else { + DiagnosticMessages::from_error(e) + } + })?; logger.success("Policies checked"); } else { logger.success("No policy found"); } + Ok(()) } /// Resolve the semantic convention specifications and return the resolved schema. diff --git a/src/registry/resolve.rs b/src/registry/resolve.rs index 07929915..3fcd1c23 100644 --- a/src/registry/resolve.rs +++ b/src/registry/resolve.rs @@ -8,6 +8,7 @@ use clap::{Args, ValueEnum}; use serde::Serialize; use weaver_cache::Cache; +use weaver_common::error::ExitIfError; use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; use weaver_semconv::registry::SemConvRegistry; @@ -74,7 +75,7 @@ pub(crate) fn command( ®istry_path, cache, logger.clone(), - ); + ).exit_if_error(logger.clone()); let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); let schema = resolve_semconv_specs(&mut registry, logger.clone()); diff --git a/src/registry/stats.rs b/src/registry/stats.rs index 5850af8d..2a22e983 100644 --- a/src/registry/stats.rs +++ b/src/registry/stats.rs @@ -5,6 +5,7 @@ use crate::registry::{load_semconv_specs, resolve_semconv_specs, RegistryArgs, semconv_registry_path_from}; use clap::Args; use weaver_cache::Cache; +use weaver_common::error::ExitIfError; use weaver_common::Logger; use weaver_resolved_schema::registry::{CommonGroupStats, GroupStats}; use weaver_resolved_schema::ResolvedTelemetrySchema; @@ -38,7 +39,7 @@ pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: & ®istry_path, cache, logger.clone(), - ); + ).exit_if_error(logger.clone()); let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); display_semconv_registry_stats(®istry); diff --git a/templates/errors/console/errors.txt.j2 b/templates/errors/console/errors.txt.j2 new file mode 100644 index 00000000..c48075ca --- /dev/null +++ b/templates/errors/console/errors.txt.j2 @@ -0,0 +1,15 @@ +{{- template.set_output_type("stdout") }} + +{{ "Diagnostic report" | bold | red }}: +{% for item in ctx %} +{%- if item.error.type == "policy_violation" %} +Violation: {{ item.error.violation.id | bold | green }} + - Category : {{ item.error.violation.category | cyan }} + - Type : {{ item.error.violation.type | cyan }} + - SemConv group : {{ item.error.violation.group | cyan }} + - SemConv attribute: {{ item.error.violation.attr | cyan }} + - Provenance: {{ item.error.provenance | cyan }} +{% else %} +{{ item.diagnostic.ansi_message }} +{% endif %} +{%- endfor %} \ No newline at end of file diff --git a/templates/errors/console/weaver.yaml b/templates/errors/console/weaver.yaml new file mode 100644 index 00000000..c7067806 --- /dev/null +++ b/templates/errors/console/weaver.yaml @@ -0,0 +1,4 @@ +templates: + - pattern: errors.txt.j2 + filter: . + application_mode: single \ No newline at end of file diff --git a/templates/errors/gh_workflow_command/errors.txt.j2 b/templates/errors/gh_workflow_command/errors.txt.j2 new file mode 100644 index 00000000..ed586fa7 --- /dev/null +++ b/templates/errors/gh_workflow_command/errors.txt.j2 @@ -0,0 +1 @@ +{{ debug() }} \ No newline at end of file diff --git a/templates/errors/gh_workflow_command/weaver.yaml b/templates/errors/gh_workflow_command/weaver.yaml new file mode 100644 index 00000000..c7067806 --- /dev/null +++ b/templates/errors/gh_workflow_command/weaver.yaml @@ -0,0 +1,4 @@ +templates: + - pattern: errors.txt.j2 + filter: . + application_mode: single \ No newline at end of file diff --git a/templates/errors/json/errors.json.j2 b/templates/errors/json/errors.json.j2 new file mode 100644 index 00000000..9a4b6bfd --- /dev/null +++ b/templates/errors/json/errors.json.j2 @@ -0,0 +1,2 @@ +{{- template.set_output_type("stdout") -}} +{{ ctx | tojson(true) }} \ No newline at end of file diff --git a/templates/errors/json/weaver.yaml b/templates/errors/json/weaver.yaml new file mode 100644 index 00000000..e2745149 --- /dev/null +++ b/templates/errors/json/weaver.yaml @@ -0,0 +1,4 @@ +templates: + - pattern: errors.json.j2 + filter: . + application_mode: single \ No newline at end of file From acae8bd2a02dd76ef6e6479fc33ca8eb51e47e1b Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 14 May 2024 15:53:44 -0700 Subject: [PATCH 03/17] chore(diagnostic): Render diagnostics from Jinja templates into gh_workflow_command format. --- .../errors/gh_workflow_command/errors.txt.j2 | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/templates/errors/gh_workflow_command/errors.txt.j2 b/templates/errors/gh_workflow_command/errors.txt.j2 index ed586fa7..ad96a03d 100644 --- a/templates/errors/gh_workflow_command/errors.txt.j2 +++ b/templates/errors/gh_workflow_command/errors.txt.j2 @@ -1 +1,19 @@ -{{ debug() }} \ No newline at end of file +{{- template.set_output_type("stdout") }} +{%- set policy_violations = ctx | selectattr("error.type", "equalto", "policy_violation") | list %} +{%- set other_diagnostics = ctx | rejectattr("error.type", "equalto", "policy_violation") | list %} + +{%- if policy_violations %} +::group::Policy violation report +{%- for item in policy_violations %} +::error file={{ item.error.provenance }}, title={{ item.error.violation.id }}::group={{ item.error.violation.group }}, attr={{ item.error.violation.attr }} +{%- endfor %} +::endgroup:: +{% endif %} + +{%- if other_diagnostics %} +::group::Diagnostic report +{% for item in policy_violations %} +{{ item.diagnostic.ansi_message }} +{% endfor %} +::endgroup:: +{% endif %} \ No newline at end of file From 4cae210b62efe97ba3a9ff7c225d4489c7468c2f Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 14 May 2024 17:17:10 -0700 Subject: [PATCH 04/17] chore(diagnostic): Fix build issues. --- Cargo.lock | 27 +- Cargo.toml | 1 - .../weaver_cache/allowed-external-types.toml | 1 + crates/weaver_cache/src/lib.rs | 2 +- crates/weaver_checker/Cargo.toml | 1 - .../allowed-external-types.toml | 3 +- crates/weaver_checker/src/lib.rs | 69 +++--- .../weaver_common/allowed-external-types.toml | 4 +- crates/weaver_common/examples/diag_service.rs | 111 --------- crates/weaver_common/src/diag/channel.rs | 28 --- .../src/diag/consumer/console.rs | 70 ------ crates/weaver_common/src/diag/consumer/mod.rs | 16 -- crates/weaver_common/src/diag/mod.rs | 230 ------------------ crates/weaver_common/src/diagnostic.rs | 12 +- crates/weaver_common/src/error.rs | 9 +- crates/weaver_common/src/lib.rs | 3 +- .../weaver_forge/allowed-external-types.toml | 1 + crates/weaver_forge/src/error.rs | 2 +- .../weaver_forge/src/extensions/ansi_code.rs | 2 +- crates/weaver_forge/src/extensions/mod.rs | 2 +- crates/weaver_forge/src/lib.rs | 44 ++-- .../allowed-external-types.toml | 2 + crates/weaver_resolver/src/lib.rs | 85 +++---- .../allowed-external-types.toml | 1 + .../allowed-external-types.toml | 4 +- crates/weaver_semconv_gen/src/gen.rs | 100 +++++--- crates/weaver_semconv_gen/src/lib.rs | 6 +- deny.toml | 12 +- src/main.rs | 10 +- src/registry/check.rs | 22 +- src/registry/generate.rs | 19 +- src/registry/mod.rs | 49 ++-- src/registry/resolve.rs | 17 +- src/registry/stats.rs | 17 +- 34 files changed, 286 insertions(+), 696 deletions(-) delete mode 100644 crates/weaver_common/examples/diag_service.rs delete mode 100644 crates/weaver_common/src/diag/channel.rs delete mode 100644 crates/weaver_common/src/diag/consumer/console.rs delete mode 100644 crates/weaver_common/src/diag/consumer/mod.rs delete mode 100644 crates/weaver_common/src/diag/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9e186526..cd02193b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,9 +587,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -954,9 +954,9 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180b130a4a41870edfbd36ce4169c7090bca70e195da783dea088dd973daa59c" +checksum = "367ee9093b0c2b04fd04c5c7c8b6a1082713534eab537597ae343663a518fa99" dependencies = [ "bstr", "itoa", @@ -2145,9 +2145,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -2210,11 +2210,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -2661,9 +2660,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "regorus" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac45cc36ed8e6188529f44deff00c078d9cd0e6cdc831c4dde96c9d6eeb3a46c" +checksum = "77dd872918e5c172bd42ac49716f89a15e35be513bba3d902e355a531529a87f" dependencies = [ "anyhow", "chrono", @@ -3078,9 +3077,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.61" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -3597,7 +3596,6 @@ name = "weaver" version = "0.1.0" dependencies = [ "clap", - "miette", "rayon", "serde", "serde_json", @@ -3637,7 +3635,6 @@ dependencies = [ "thiserror", "walkdir", "weaver_common", - "weaver_forge", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a9dfcde8..e53ee6dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,6 @@ serde.workspace = true serde_yaml.workspace = true serde_json.workspace = true walkdir.workspace = true -miette.workspace = true rayon = "1.10.0" diff --git a/crates/weaver_cache/allowed-external-types.toml b/crates/weaver_cache/allowed-external-types.toml index f186b677..95fadf37 100644 --- a/crates/weaver_cache/allowed-external-types.toml +++ b/crates/weaver_cache/allowed-external-types.toml @@ -3,4 +3,5 @@ # This is used with cargo-check-external-types to reduce the surface area of downstream crates from # the public API. Ideally this can have a few exceptions as possible. allowed_external_types = [ + "serde::ser::Serialize", ] \ No newline at end of file diff --git a/crates/weaver_cache/src/lib.rs b/crates/weaver_cache/src/lib.rs index 086f4561..4d8701ff 100644 --- a/crates/weaver_cache/src/lib.rs +++ b/crates/weaver_cache/src/lib.rs @@ -17,8 +17,8 @@ use gix::clone::PrepareFetch; use gix::create::Kind; use gix::remote::fetch::Shallow; use gix::{create, open, progress}; -use tempdir::TempDir; use serde::Serialize; +use tempdir::TempDir; /// An error that can occur while creating or using a cache. #[derive(thiserror::Error, Debug, Serialize)] diff --git a/crates/weaver_checker/Cargo.toml b/crates/weaver_checker/Cargo.toml index 096a7c27..306b7bf4 100644 --- a/crates/weaver_checker/Cargo.toml +++ b/crates/weaver_checker/Cargo.toml @@ -15,7 +15,6 @@ workspace = true [dependencies] weaver_common = { path = "../weaver_common" } -weaver_forge = { path = "../weaver_forge" } thiserror.workspace = true serde.workspace = true diff --git a/crates/weaver_checker/allowed-external-types.toml b/crates/weaver_checker/allowed-external-types.toml index 434ff42f..06a05a3f 100644 --- a/crates/weaver_checker/allowed-external-types.toml +++ b/crates/weaver_checker/allowed-external-types.toml @@ -5,5 +5,6 @@ allowed_external_types = [ "serde::ser::Serialize", "serde::de::Deserialize", - "weaver_common::error::WeaverError" + "weaver_common::error::WeaverError", + "miette::protocol::Diagnostic", ] \ No newline at end of file diff --git a/crates/weaver_checker/src/lib.rs b/crates/weaver_checker/src/lib.rs index 79b75ef0..26361527 100644 --- a/crates/weaver_checker/src/lib.rs +++ b/crates/weaver_checker/src/lib.rs @@ -13,8 +13,8 @@ use walkdir::DirEntry; use weaver_common::error::{format_errors, handle_errors, WeaverError}; -use crate::Error::CompoundError; use crate::violation::Violation; +use crate::Error::CompoundError; pub mod violation; @@ -166,15 +166,11 @@ impl Engine { /// # Returns /// /// The number of policies added. - pub fn add_policies>(&mut self, policy_dir: P, policy_glob_pattern: &str) -> Result { - let mut errors = Vec::new(); - let mut added_policy_count = 0; - - let policy_glob = Glob::new(policy_glob_pattern).map_err(|e| Error::InvalidPolicyGlobPattern { - pattern: policy_glob_pattern.to_owned(), - error: e.to_string(), - })?.compile_matcher(); - + pub fn add_policies>( + &mut self, + policy_dir: P, + policy_glob_pattern: &str, + ) -> Result { fn is_hidden(entry: &DirEntry) -> bool { entry .file_name() @@ -182,23 +178,32 @@ impl Engine { .map(|s| s.starts_with('.')) .unwrap_or(false) } + + let mut errors = Vec::new(); + let mut added_policy_count = 0; + + let policy_glob = Glob::new(policy_glob_pattern) + .map_err(|e| Error::InvalidPolicyGlobPattern { + pattern: policy_glob_pattern.to_owned(), + error: e.to_string(), + })? + .compile_matcher(); + let is_policy_file = |entry: &DirEntry| -> bool { let path = entry.path().to_string_lossy(); policy_glob.is_match(path.as_ref()) }; // Visit recursively all the files in the policy directory - for entry in walkdir::WalkDir::new(policy_dir) { - if let Ok(entry) = entry { - if is_hidden(&entry) { - continue; - } - if is_policy_file(&entry) { - if let Err(err) = self.add_policy(entry.path()) { - errors.push(err); - } else { - added_policy_count += 1; - } + for entry in walkdir::WalkDir::new(policy_dir).into_iter().flatten() { + if is_hidden(&entry) { + continue; + } + if is_policy_file(&entry) { + if let Err(err) = self.add_policy(entry.path()) { + errors.push(err); + } else { + added_policy_count += 1; } } } @@ -264,7 +269,7 @@ impl Engine { pub fn check(&mut self, package: PolicyPackage) -> Result, Error> { let value = self .engine - .eval_rule(format!("data.{}.deny", package.to_string())) + .eval_rule(format!("data.{}.deny", package)) .map_err(|e| Error::ViolationEvaluationError { error: e.to_string(), })?; @@ -292,8 +297,8 @@ mod tests { use weaver_common::error::format_errors; - use crate::{Engine, Error, PolicyPackage}; use crate::violation::Violation; + use crate::{Engine, Error, PolicyPackage}; #[test] fn test_policy() -> Result<(), Box> { @@ -328,9 +333,9 @@ mod tests { attr: "protocol.port".to_owned(), }, ] - .into_iter() - .map(|v| (v.id().to_owned(), v)) - .collect(); + .into_iter() + .map(|v| (v.id().to_owned(), v)) + .collect(); let violations = engine.check(PolicyPackage::BeforeResolution)?; assert_eq!(violations.len(), 3); @@ -381,8 +386,7 @@ mod tests { #[test] fn test_add_policies() -> Result<(), Box> { let mut engine = Engine::new(); - let result = engine - .add_policies("data/registries", "*.rego"); + let result = engine.add_policies("data/registries", "*.rego"); assert!(result.is_ok()); @@ -414,9 +418,9 @@ mod tests { attr: "protocol.port".to_owned(), }, ] - .into_iter() - .map(|v| (v.id().to_owned(), v)) - .collect(); + .into_iter() + .map(|v| (v.id().to_owned(), v)) + .collect(); let violations = engine.check(PolicyPackage::BeforeResolution)?; assert_eq!(violations.len(), 3); @@ -431,8 +435,7 @@ mod tests { #[test] fn test_add_policies_with_invalid_policies() { let mut engine = Engine::new(); - let result = engine - .add_policies("data/policies", "*.rego"); + let result = engine.add_policies("data/policies", "*.rego"); // We have 2 invalid Rego files in data/policies assert!(result.is_err()); diff --git a/crates/weaver_common/allowed-external-types.toml b/crates/weaver_common/allowed-external-types.toml index 34aa778b..88800138 100644 --- a/crates/weaver_common/allowed-external-types.toml +++ b/crates/weaver_common/allowed-external-types.toml @@ -3,6 +3,6 @@ # This is used with cargo-check-external-types to reduce the surface area of downstream crates from # the public API. Ideally this can have a few exceptions as possible. allowed_external_types = [ - "miette::protocol::Diagnostic", - "miette::eyreish::Report", + "miette::protocol::*", + "serde::ser::Serialize", ] \ No newline at end of file diff --git a/crates/weaver_common/examples/diag_service.rs b/crates/weaver_common/examples/diag_service.rs deleted file mode 100644 index 83c22788..00000000 --- a/crates/weaver_common/examples/diag_service.rs +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! An example that demonstrates how to use the diagnostic service. - -use crate::DiagnosticMessages::{AnAdvice, Message, MyError, TheWarning}; -use miette::{Diagnostic, NamedSource, SourceSpan}; -use thiserror::Error; -use weaver_common::diag::channel::DiagChannel; -use weaver_common::diag::consumer::console::ConsoleDiagMessageConsumer; -use weaver_common::diag::DiagService; - -#[derive(Error, Diagnostic, Debug)] -enum DiagnosticMessages { - #[error("A fantastic diagnostic error!")] - #[diagnostic( - code(oops::my::bad), - severity(Error), - url(docsrs), - help("try doing it better next time?") - )] - MyError { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, - - #[error("A fantastic diagnostic advice!")] - #[diagnostic( - code(oops::my::bad), - severity(Advice), - url(docsrs), - help("try doing it better next time?") - )] - AnAdvice { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, - - #[error("A fantastic diagnostic warning!")] - #[diagnostic( - code(oops::my::bad), - severity(Warning), - url(docsrs), - help("try doing it better next time?") - )] - TheWarning { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, - - #[error("A fantastic diagnostic message!")] - #[diagnostic( - code(oops::my::bad), - url(docsrs), - help("try doing it better next time?") - )] - Message { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, -} - -fn main() { - let consumer = ConsoleDiagMessageConsumer::new(true); - let service = DiagService::new(consumer, 10); - let channel = service.channel(); - - app_code(&channel); - - service.stop(); -} - -fn app_code(diag_channel: &DiagChannel) { - let src = "source\n text\n here".to_owned(); - - diag_channel.report(MyError { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(AnAdvice { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(TheWarning { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(Message { - src: NamedSource::new("bad_file.rs", src), - bad_bit: (9, 4).into(), - }); -} diff --git a/crates/weaver_common/src/diag/channel.rs b/crates/weaver_common/src/diag/channel.rs deleted file mode 100644 index 819eb779..00000000 --- a/crates/weaver_common/src/diag/channel.rs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! A channel for reporting diagnostic messages. - -use crate::diag::SystemMessage; -use miette::Diagnostic; -use std::error::Error; -use std::sync::mpsc::SyncSender; - -/// A channel for reporting diagnostic messages. -#[derive(Clone)] -pub struct DiagChannel { - sender: SyncSender, -} - -impl DiagChannel { - /// Create a new diagnostic channel. - pub(crate) fn new(sender: SyncSender) -> Self { - Self { sender } - } - - /// Report a diagnostic message. - pub fn report(&self, message: M) { - // JUSTIFICATION: The only way this can fail is if the receiver has been dropped. - self.sender.send(SystemMessage::Diagnostic(message.into())) - .expect("The DiagService has been stopped while the application is still running and generating diagnostic messages. Please ensure that the DiagService is stopped only after the rest of the application has finished."); - } -} diff --git a/crates/weaver_common/src/diag/consumer/console.rs b/crates/weaver_common/src/diag/consumer/console.rs deleted file mode 100644 index 599fb198..00000000 --- a/crates/weaver_common/src/diag/consumer/console.rs +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! A consumer that writes diagnostic messages to the console as plain text. - -#![allow(clippy::print_stdout)] -#![allow(clippy::print_stderr)] - -use crate::diag::consumer::DiagMessageConsumer; -use crate::diag::SystemMessage; -use std::sync::mpsc; - -/// A consumer that writes diagnostic messages to the console as plain text. -pub struct ConsoleDiagMessageConsumer { - enable_output_locking: bool, -} - -impl ConsoleDiagMessageConsumer { - /// Creates a new console consumer. - /// - /// If `enable_output_locking` is true, the output (stdout and stderr) will be locked to speed - /// up the output to the console. - #[must_use] - pub fn new(enable_output_locking: bool) -> Self { - Self { - enable_output_locking, - } - } -} - -impl DiagMessageConsumer for ConsoleDiagMessageConsumer { - /// Runs the console consumer. - /// The consumer is expected to consume diagnostic messages from the given receiver, report - /// them, and handle the `SystemMessage::Stop` message. - fn run(&self, receiver: mpsc::Receiver) { - let stdout = std::io::stdout(); - let stderr = std::io::stderr(); - let lock = if self.enable_output_locking { - // Used to speed up the output to the console. - Some((stdout.lock(), stderr.lock())) - } else { - None - }; - - for message in receiver { - match message { - SystemMessage::Diagnostic(report) => { - if let Some(severity) = report.severity() { - match severity { - miette::Severity::Advice => { - println!("Advice: {:?}", report); - } - miette::Severity::Warning => { - eprintln!("Warning: {:?}", report); - } - miette::Severity::Error => { - eprintln!("Error: {:?}", report); - } - } - } else { - println!("{:?}", report); - } - } - SystemMessage::Stop => { - break; - } - } - } - drop(lock); - } -} diff --git a/crates/weaver_common/src/diag/consumer/mod.rs b/crates/weaver_common/src/diag/consumer/mod.rs deleted file mode 100644 index 21eec523..00000000 --- a/crates/weaver_common/src/diag/consumer/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! A consumer that consumes diagnostic messages. - -pub mod console; - -use crate::diag::SystemMessage; -use std::sync::mpsc; - -/// A consumer of diagnostic messages. -pub trait DiagMessageConsumer: Send { - /// Runs the consumer. - /// The consumer is expected to consume diagnostic messages from the given receiver, report - /// them, and stop when the `SystemMessage::Stop` message is received. - fn run(&self, receiver: mpsc::Receiver); -} diff --git a/crates/weaver_common/src/diag/mod.rs b/crates/weaver_common/src/diag/mod.rs deleted file mode 100644 index aa82aaac..00000000 --- a/crates/weaver_common/src/diag/mod.rs +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Diagnostic infrastructure used to report diagnostic messages to the user. -//! The diagnostic messages are based on the [`miette`] crate. -//! This infrastructure is designed to be extensible and flexible. - -use std::sync::mpsc::{sync_channel, SyncSender}; -use std::thread; - -use miette::Report; - -use crate::diag::channel::DiagChannel; - -pub mod channel; -pub mod consumer; - -/// A system message that can be sent to the diagnostic service. -pub enum SystemMessage { - /// A diagnostic report. - Diagnostic(Report), - /// A stop message used to stop the diagnostic service. - Stop, -} - -/// A diagnostic service that consumes diagnostic messages and reports them to a consumer. -/// The service runs in a separate thread. -pub struct DiagService { - sender: SyncSender, - join_handle: thread::JoinHandle<()>, -} - -impl DiagService { - /// Creates a new diagnostic service given a [`consumer::DiagMessageConsumer`] and a bound. - /// The bound is the maximum number of messages that can be buffered. - /// If the bound is reached, the sender will block until the buffer is freed. - /// The consumer will consume the messages in the order they were sent. - /// The service will run in a separate thread. - /// - /// The service will stop when the [`DiagService::stop`] method is called. - pub fn new(consumer: impl consumer::DiagMessageConsumer + 'static, bound: usize) -> Self { - let (sender, receiver) = sync_channel(bound); - let join_handle = thread::spawn(move || { - consumer.run(receiver); - }); - - Self { - sender, - join_handle, - } - } - - /// Returns a channel for reporting diagnostic reports. - #[must_use] - pub fn channel(&self) -> DiagChannel { - DiagChannel::new(self.sender.clone()) - } - - /// Waits for the diagnostic service to finish. - /// This method should be called at the end of the program. - /// If this method is not called, the program will hang. - /// This method should be called only once. - pub fn stop(self) { - self.sender - .send(SystemMessage::Stop) - .expect("The DiagService has already been stopped."); - self.join_handle - .join() - .expect("The DiagService thread has panicked."); - } -} - -#[cfg(test)] -mod tests { - use miette::{Diagnostic, NamedSource, SourceSpan}; - use thiserror::Error; - - use crate::diag::consumer::console::ConsoleDiagMessageConsumer; - - use super::*; - - #[derive(Error, Diagnostic, Debug)] - enum DiagMessages { - #[error("A fantastic diagnostic error!")] - #[diagnostic( - code(oops::my::bad), - severity(Error), - url(docsrs), - help("try doing it better next time?") - )] - Error { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, - - #[error("A fantastic diagnostic advice!")] - #[diagnostic( - code(oops::my::bad), - severity(Advice), - url(docsrs), - help("try doing it better next time?") - )] - Advice { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, - - #[error("A fantastic diagnostic warning!")] - #[diagnostic( - code(oops::my::bad), - severity(Warning), - url(docsrs), - help("try doing it better next time?") - )] - Warning { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, - - #[error("A fantastic diagnostic message!")] - #[diagnostic( - code(oops::my::bad), - url(docsrs), - help("try doing it better next time?") - )] - Message { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, - }, - #[error("Compound errors")] - CompoundError { - #[related] - errors: Vec, - } - } - - #[test] - fn test_console_diag_service() { - let consumer = ConsoleDiagMessageConsumer::new(true); - let service = DiagService::new(consumer, 10); - let channel = service.channel(); - - single_thread_app(&channel); - multi_thread_app(&channel); - - service.stop(); - } - - #[test] - fn test_console_diag_service_without_stdout_lock() { - let consumer = ConsoleDiagMessageConsumer::new(false); - let service = DiagService::new(consumer, 10); - let channel = service.channel(); - - single_thread_app(&channel); - multi_thread_app(&channel); - - service.stop(); - } - - /// This function represent a single threaded application that reports a diagnostic message. - fn single_thread_app(diag_channel: &DiagChannel) { - let src = "source\n text\n here".to_owned(); - - diag_channel.report(DiagMessages::Error { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(DiagMessages::Advice { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(DiagMessages::Warning { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(DiagMessages::Message { - src: NamedSource::new("bad_file.rs", src), - bad_bit: (9, 4).into(), - }); - } - - /// This function represent a multithreaded application that reports a diagnostic message. - /// Note that the Rust compiler will force you to clone the `DiagChannel` to pass it to the - /// thread. - fn multi_thread_app(diag_channel: &DiagChannel) { - let diag_channel = diag_channel.clone(); - - _ = thread::spawn(move || { - let src = "source\n text\n here".to_owned(); - - diag_channel.report(DiagMessages::Error { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(DiagMessages::Advice { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(DiagMessages::Warning { - src: NamedSource::new("bad_file.rs", src.clone()), - bad_bit: (9, 4).into(), - }); - diag_channel.report(DiagMessages::Message { - src: NamedSource::new("bad_file.rs", src), - bad_bit: (9, 4).into(), - }); - }) - .join(); - } -} diff --git a/crates/weaver_common/src/diagnostic.rs b/crates/weaver_common/src/diagnostic.rs index d525c0ac..d1a9007c 100644 --- a/crates/weaver_common/src/diagnostic.rs +++ b/crates/weaver_common/src/diagnostic.rs @@ -2,10 +2,10 @@ //! A generic diagnostic message -use std::error::Error; +use crate::error::WeaverDiagnostic; use miette::{Diagnostic, LabeledSpan, Report, Severity}; use serde::Serialize; -use crate::error::WeaverDiagnostic; +use std::error::Error; /// An extension to the [`miette::Diagnostic`] struct that adds an ansi message /// representation of the diagnostic message. @@ -82,7 +82,9 @@ impl DiagnosticMessage { impl DiagnosticMessages { /// Creates a new list of diagnostic messages for a list of errors - pub fn from_errors(errors: Vec) -> Self { + pub fn from_errors( + errors: Vec, + ) -> Self { Self(errors.into_iter().map(DiagnosticMessage::new).collect()) } @@ -92,7 +94,9 @@ impl DiagnosticMessages { } } -impl From for DiagnosticMessages { +impl From + for DiagnosticMessages +{ /// Convert errors marked with the DiagnosticMessage trait into a DiagnosticMessages. fn from(error: T) -> Self { Self(vec![DiagnosticMessage::new(error)]) diff --git a/crates/weaver_common/src/error.rs b/crates/weaver_common/src/error.rs index b0777432..18f705e5 100644 --- a/crates/weaver_common/src/error.rs +++ b/crates/weaver_common/src/error.rs @@ -5,9 +5,9 @@ //! a consistent way. use crate::Logger; -use std::process::exit; use miette::Diagnostic; use serde::Serialize; +use std::process::exit; /// A trait marker for Weaver diagnostic. pub trait WeaverDiagnostic {} @@ -17,13 +17,10 @@ pub trait WeaverDiagnostic {} /// /// This allows any type that implements `Diagnostic` and `Serialize` to be /// converted into [crate::diagnostic::DiagnosticMessages]. -impl WeaverDiagnostic for T - where - T: Serialize + Diagnostic + Send + Sync + ?Sized, -{ } +impl WeaverDiagnostic for T where T: Serialize + Diagnostic + Send + Sync + ?Sized {} /// A trait for custom error handling in the `weaver` crates. -pub trait WeaverError : Serialize + Diagnostic + Send + Sync { +pub trait WeaverError: Serialize + Diagnostic + Send + Sync { /// Retrieves a list of error messages associated with this error. /// For compound errors, this method should return a list of all /// error messages. For simple errors, this method should return diff --git a/crates/weaver_common/src/lib.rs b/crates/weaver_common/src/lib.rs index c49ae2b3..c2e74b1e 100644 --- a/crates/weaver_common/src/lib.rs +++ b/crates/weaver_common/src/lib.rs @@ -2,11 +2,10 @@ #![doc = include_str!("../README.md")] -pub mod diag; +pub mod diagnostic; pub mod error; pub mod in_memory; pub mod quiet; -pub mod diagnostic; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, Mutex}; diff --git a/crates/weaver_forge/allowed-external-types.toml b/crates/weaver_forge/allowed-external-types.toml index 4333d3a1..72d1780e 100644 --- a/crates/weaver_forge/allowed-external-types.toml +++ b/crates/weaver_forge/allowed-external-types.toml @@ -10,4 +10,5 @@ allowed_external_types = [ "weaver_resolved_schema::*", "weaver_semconv::*", "minijinja::value::Value", + "miette::protocol::Diagnostic", ] \ No newline at end of file diff --git a/crates/weaver_forge/src/error.rs b/crates/weaver_forge/src/error.rs index 7b86ce99..40923a78 100644 --- a/crates/weaver_forge/src/error.rs +++ b/crates/weaver_forge/src/error.rs @@ -3,9 +3,9 @@ //! Error types and utilities. use crate::error::Error::CompoundError; -use std::{path::PathBuf, str::FromStr}; use miette::Diagnostic; use serde::Serialize; +use std::{path::PathBuf, str::FromStr}; use weaver_common::error::WeaverError; use weaver_resolved_schema::attribute::AttributeRef; diff --git a/crates/weaver_forge/src/extensions/ansi_code.rs b/crates/weaver_forge/src/extensions/ansi_code.rs index 673bd7f7..e2b7354f 100644 --- a/crates/weaver_forge/src/extensions/ansi_code.rs +++ b/crates/weaver_forge/src/extensions/ansi_code.rs @@ -218,4 +218,4 @@ pub(crate) fn underline(input: &Value) -> String { #[must_use] pub(crate) fn strikethrough(input: &Value) -> String { format!("\x1b[9m{}\x1b[0m", input) -} \ No newline at end of file +} diff --git a/crates/weaver_forge/src/extensions/mod.rs b/crates/weaver_forge/src/extensions/mod.rs index 90651ffc..6bcf892f 100644 --- a/crates/weaver_forge/src/extensions/mod.rs +++ b/crates/weaver_forge/src/extensions/mod.rs @@ -2,7 +2,7 @@ //! Custom filters used by the template engine. pub mod acronym; +pub mod ansi_code; pub mod case_converter; pub mod code; pub mod otel; -pub mod ansi_code; diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index a3e96975..4c04652b 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -2,15 +2,15 @@ #![doc = include_str!("../README.md")] -use std::{fs, io}; use std::borrow::Cow; use std::fmt::{Debug, Display, Formatter}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; +use std::{fs, io}; -use minijinja::{Environment, ErrorKind, State, Value}; use minijinja::syntax::SyntaxConfig; use minijinja::value::{from_args, Object}; +use minijinja::{Environment, ErrorKind, State, Value}; use rayon::iter::IntoParallelIterator; use rayon::iter::ParallelIterator; use serde::Serialize; @@ -107,11 +107,11 @@ impl Object for TemplateObject { args: &[Value], ) -> Result { if name == "set_file_name" { - let (file_name, ): (&str, ) = from_args(args)?; + let (file_name,): (&str,) = from_args(args)?; file_name.clone_into(&mut self.file_name.lock().expect("Lock poisoned")); Ok(Value::from("")) } else if name == "set_output_type" { - let (output_type, ): (&str, ) = from_args(args)?; + let (output_type,): (&str,) = from_args(args)?; let output_type = match output_type { "stdout" => OutputType::Stdout, "stderr" => OutputType::Stderr, @@ -295,8 +295,8 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, output_dir, ) { @@ -330,8 +330,8 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, output_dir, ) { @@ -347,6 +347,8 @@ impl TemplateEngine { handle_errors(errs) } + #[allow(clippy::print_stdout)] // This is a CLI tool, so it's fine to print to stdout + #[allow(clippy::print_stderr)] // This is a CLI tool, so it's fine to print to stderr fn evaluate_template( &self, log: impl Logger + Clone + Sync, @@ -389,7 +391,12 @@ impl TemplateEngine { error_id: e.to_string(), error: error_summary(e), })?; - match template_object.output_type.lock().expect("Lock poisoned").clone() { + match template_object + .output_type + .lock() + .expect("Lock poisoned") + .clone() + { OutputType::Stdout => { println!("{}", output); } @@ -532,7 +539,10 @@ impl TemplateEngine { env.add_filter("bg_bright_green", extensions::ansi_code::bg_bright_green); env.add_filter("bg_bright_yellow", extensions::ansi_code::bg_bright_yellow); env.add_filter("bg_bright_blue", extensions::ansi_code::bg_bright_blue); - env.add_filter("bg_bright_magenta", extensions::ansi_code::bg_bright_magenta); + env.add_filter( + "bg_bright_magenta", + extensions::ansi_code::bg_bright_magenta, + ); env.add_filter("bg_bright_cyan", extensions::ansi_code::bg_bright_cyan); env.add_filter("bg_bright_white", extensions::ansi_code::bg_bright_white); @@ -601,7 +611,7 @@ fn cross_platform_loader<'x, P: AsRef + 'x>( ErrorKind::InvalidOperation, "could not read template", ) - .with_source(err)), + .with_source(err)), } } } @@ -835,12 +845,12 @@ mod tests { schema.registry(registry_id).expect("registry not found"), schema.catalog(), ) - .unwrap_or_else(|e| { - panic!( - "Failed to create the context for the template evaluation: {:?}", - e - ) - }); + .unwrap_or_else(|e| { + panic!( + "Failed to create the context for the template evaluation: {:?}", + e + ) + }); engine .generate( diff --git a/crates/weaver_resolver/allowed-external-types.toml b/crates/weaver_resolver/allowed-external-types.toml index 1c0bcd1e..89ff68f7 100644 --- a/crates/weaver_resolver/allowed-external-types.toml +++ b/crates/weaver_resolver/allowed-external-types.toml @@ -3,7 +3,9 @@ # This is used with cargo-check-external-types to reduce the surface area of downstream crates from # the public API. Ideally this can have a few exceptions as possible. allowed_external_types = [ + "serde::ser::Serialize", "serde::de::Deserialize", + "miette::protocol::Diagnostic", "weaver_resolved_schema::*", "weaver_semconv::*", diff --git a/crates/weaver_resolver/src/lib.rs b/crates/weaver_resolver/src/lib.rs index 2b09fb09..ff2f031a 100644 --- a/crates/weaver_resolver/src/lib.rs +++ b/crates/weaver_resolver/src/lib.rs @@ -2,9 +2,9 @@ #![doc = include_str!("../README.md")] -use std::collections::HashMap; -use std::path::{MAIN_SEPARATOR, PathBuf}; use miette::Diagnostic; +use std::collections::HashMap; +use std::path::{PathBuf, MAIN_SEPARATOR}; use rayon::iter::ParallelBridge; use rayon::iter::ParallelIterator; @@ -241,14 +241,10 @@ impl SchemaResolver { cache: &Cache, ) -> Result<(PathBuf, String), Error> { match registry_path { - RegistryPath::Local { path_pattern: path } => { - Ok((path.into(), path.clone())) - } + RegistryPath::Local { path_pattern: path } => Ok((path.into(), path.clone())), RegistryPath::GitUrl { git_url, path } => { match cache.git_repo(git_url.clone(), path.clone()) { - Ok(local_git_repo) => { - Ok((local_git_repo, git_url.clone())) - } + Ok(local_git_repo) => Ok((local_git_repo, git_url.clone())), Err(e) => Err(Error::SemConvError { message: e.to_string(), }), @@ -287,49 +283,48 @@ impl SchemaResolver { // Loads the semantic convention specifications from the git repo. // All yaml files are recursively loaded and parsed in parallel from // the given path. - let result = walkdir::WalkDir::new(local_path.clone()) - .into_iter() - .filter_entry(|e| !is_hidden(e)) - .par_bridge() - .filter_map(|entry| { - match entry { - Ok(entry) => { - if !is_semantic_convention_file(&entry) { - return None; - } + let result = + walkdir::WalkDir::new(local_path.clone()) + .into_iter() + .filter_entry(|e| !is_hidden(e)) + .par_bridge() + .filter_map(|entry| { + match entry { + Ok(entry) => { + if !is_semantic_convention_file(&entry) { + return None; + } - let spec = - SemConvRegistry::semconv_spec_from_file(entry.path()).map_err(|e| { - Error::SemConvError { + let spec = SemConvRegistry::semconv_spec_from_file(entry.path()) + .map_err(|e| Error::SemConvError { message: e.to_string(), + }); + match spec { + Ok((path, spec)) => { + // Replace the local path with the git URL combined with the relative path + // of the semantic convention file. + let prefix = local_path + .to_str() + .map(|s| s.to_owned()) + .unwrap_or_default(); + let path = if registry_path_repr.ends_with(MAIN_SEPARATOR) { + let relative_path = &path[prefix.len()..]; + format!("{}{}", registry_path_repr, relative_path) + } else { + let relative_path = &path[prefix.len() + 1..]; + format!("{}/{}", registry_path_repr, relative_path) + }; + Some(Ok((path, spec))) } - }); - match spec { - Ok((path, spec)) => { - // Replace the local path with the git URL combined with the relative path - // of the semantic convention file. - let prefix = local_path - .to_str() - .map(|s| s.to_owned()) - .unwrap_or_default(); - let path = if registry_path_repr.ends_with(MAIN_SEPARATOR) { - let relative_path = &path[prefix.len() + 0..]; - format!("{}{}", registry_path_repr, relative_path) - } else { - let relative_path = &path[prefix.len() + 1..]; - format!("{}/{}", registry_path_repr, relative_path) - }; - Some(Ok((path, spec))) + Err(e) => Some(Err(e)), } - Err(e) => Some(Err(e)), } + Err(e) => Some(Err(Error::SemConvError { + message: e.to_string(), + })), } - Err(e) => Some(Err(Error::SemConvError { - message: e.to_string(), - })), - } - }) - .collect::>(); + }) + .collect::>(); let mut error = vec![]; let result = result diff --git a/crates/weaver_semconv/allowed-external-types.toml b/crates/weaver_semconv/allowed-external-types.toml index dc92c526..5563d5bb 100644 --- a/crates/weaver_semconv/allowed-external-types.toml +++ b/crates/weaver_semconv/allowed-external-types.toml @@ -7,4 +7,5 @@ allowed_external_types = [ "serde::de::Deserialize", "weaver_common::error::WeaverError", "ordered_float::OrderedFloat", # ToDo: Remove this dependency before version 1.0 + "miette::protocol::Diagnostic", ] \ No newline at end of file diff --git a/crates/weaver_semconv_gen/allowed-external-types.toml b/crates/weaver_semconv_gen/allowed-external-types.toml index 0011217d..1e75f38d 100644 --- a/crates/weaver_semconv_gen/allowed-external-types.toml +++ b/crates/weaver_semconv_gen/allowed-external-types.toml @@ -10,5 +10,7 @@ allowed_external_types = [ "weaver_cache::Error", "weaver_common::error::WeaverError", "weaver_forge::error::Error", - "weaver_forge::TemplateEngine" + "weaver_forge::TemplateEngine", + "miette::protocol::Diagnostic", + "serde::ser::Serialize", ] diff --git a/crates/weaver_semconv_gen/src/gen.rs b/crates/weaver_semconv_gen/src/gen.rs index de3136e7..7c98e67c 100644 --- a/crates/weaver_semconv_gen/src/gen.rs +++ b/crates/weaver_semconv_gen/src/gen.rs @@ -36,7 +36,9 @@ impl GenerateMarkdownContext { /// Renders stored notes into markdown format. fn write_rendered_notes(&self, out: &mut Out) -> Result<(), Error> { for (counter, note) in self.notes.iter().enumerate() { - write!(out, "\n**[{}]:** {}\n", counter + 1, note.trim()).map_err(|e| Error::StdIoError(e.to_string())).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "\n**[{}]:** {}\n", counter + 1, note.trim()) + .map_err(|e| Error::StdIoError(e.to_string())) + .map_err(|e| Error::StdIoError(e.to_string()))?; } Ok(()) } @@ -72,10 +74,18 @@ fn write_example_list( fn write_examples_string(out: &mut Out, examples: &Examples) -> Result<(), Error> { match examples { - Examples::Bool(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), - Examples::Int(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), - Examples::Double(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), - Examples::String(value) => Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?), + Examples::Bool(value) => { + Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) + } + Examples::Int(value) => { + Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) + } + Examples::Double(value) => { + Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) + } + Examples::String(value) => { + Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) + } Examples::Ints(values) => write_example_list(out, values), Examples::Doubles(values) => write_example_list(out, values), Examples::Bools(values) => write_example_list(out, values), @@ -85,9 +95,13 @@ fn write_examples_string(out: &mut Out, examples: &Examples) -> Resu fn write_enum_value_string(out: &mut Out, value: &ValueSpec) -> Result<(), Error> { match value { - ValueSpec::Double(v) => write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?, + ValueSpec::Double(v) => { + write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?; + } ValueSpec::Int(v) => write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?, - ValueSpec::String(v) => write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?, + ValueSpec::String(v) => { + write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?; + } } Ok(()) } @@ -115,15 +129,18 @@ fn write_stability_badge( Some(Stability::Stable) => write!( out, "![Stable](https://img.shields.io/badge/-stable-lightgreen)" - ).map_err(|e| Error::StdIoError(e.to_string()))?, + ) + .map_err(|e| Error::StdIoError(e.to_string()))?, Some(Stability::Deprecated) => write!( out, "![Deprecated](https://img.shields.io/badge/-deprecated-red)" - ).map_err(|e| Error::StdIoError(e.to_string()))?, + ) + .map_err(|e| Error::StdIoError(e.to_string()))?, Some(Stability::Experimental) | None => write!( out, "![Experimental](https://img.shields.io/badge/-experimental-blue)" - ).map_err(|e| Error::StdIoError(e.to_string()))?, + ) + .map_err(|e| Error::StdIoError(e.to_string()))?, } Ok(()) } @@ -136,8 +153,10 @@ struct AttributeView<'a> { impl<'a> AttributeView<'a> { fn write_name(&self, out: &mut T) -> Result<(), Error> { match &self.attribute.r#type { - AttributeType::Template(_) => Ok(write!(out, "{}.", self.attribute.name).map_err(|e| Error::StdIoError(e.to_string()))?), - _ => Ok(write!(out, "{}", self.attribute.name).map_err(|e| Error::StdIoError(e.to_string()))?), + AttributeType::Template(_) => Ok(write!(out, "{}.", self.attribute.name) + .map_err(|e| Error::StdIoError(e.to_string()))?), + _ => Ok(write!(out, "{}", self.attribute.name) + .map_err(|e| Error::StdIoError(e.to_string()))?), } } @@ -151,7 +170,7 @@ impl<'a> AttributeView<'a> { .replace('_', "-"); // TODO - We should try to link to the name itself, instead // of just the correct group. - Ok(write!(out, "{prefix}/{reg_name}.md").map_err(|e| Error::StdIoError(e.to_string()))?) + write!(out, "{prefix}/{reg_name}.md").map_err(|e| Error::StdIoError(e.to_string())) } fn write_name_with_optional_link( @@ -189,7 +208,8 @@ impl<'a> AttributeView<'a> { write!( out, "\n| Value | Description | Stability |\n|---|---|---|\n" - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; if let AttributeType::Enum { members, .. } = &self.attribute.r#type { for m in members { write!(out, "| ").map_err(|e| Error::StdIoError(e.to_string()))?; @@ -205,7 +225,8 @@ impl<'a> AttributeView<'a> { write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; if let Some(note) = m.deprecated.as_ref() { write_stability_badge(out, &Some(Stability::Deprecated))?; - write!(out, "
{}", note.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "
{}", note.trim()) + .map_err(|e| Error::StdIoError(e.to_string()))?; } else { write_stability_badge(out, &m.stability)?; } @@ -249,7 +270,8 @@ impl<'a> AttributeView<'a> { ctx: &mut GenerateMarkdownContext, ) -> Result<(), Error> { if self.attribute.note.is_empty() { - write!(out, "{}", self.attribute.brief.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "{}", self.attribute.brief.trim()) + .map_err(|e| Error::StdIoError(e.to_string()))?; Ok(()) } else { write!( @@ -257,7 +279,8 @@ impl<'a> AttributeView<'a> { "{} {}", self.attribute.brief.trim(), ctx.add_note(self.attribute.note.clone()) - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; Ok(()) } } @@ -283,16 +306,20 @@ impl<'a> AttributeView<'a> { out, "`Conditionally Required` {}", ctx.add_note(text.clone()) - ).map_err(|e| Error::StdIoError(e.to_string()))?) + ) + .map_err(|e| Error::StdIoError(e.to_string()))?) } else { - Ok(write!(out, "`Conditionally Required` {text}").map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Conditionally Required` {text}") + .map_err(|e| Error::StdIoError(e.to_string()))?) } } RequirementLevel::Recommended { text } => { if text.len() > BREAK_COUNT { - Ok(write!(out, "`Recommended` {}", ctx.add_note(text.clone())).map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Recommended` {}", ctx.add_note(text.clone())) + .map_err(|e| Error::StdIoError(e.to_string()))?) } else { - Ok(write!(out, "`Recommended` {text}").map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Recommended` {text}") + .map_err(|e| Error::StdIoError(e.to_string()))?) } } } @@ -406,18 +433,23 @@ impl<'a> AttributeTableView<'a> { } if self.group.r#type == GroupType::Event { - write!(out, "The event name MUST be `{}`\n\n", self.event_name()).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "The event name MUST be `{}`\n\n", self.event_name()) + .map_err(|e| Error::StdIoError(e.to_string()))?; } if args.is_omit_requirement() { writeln!( out, "| Attribute | Type | Description | Examples | Stability |" - ).map_err(|e| Error::StdIoError(e.to_string()))?; - writeln!(out, "|---|---|---|---|---|").map_err(|e| Error::StdIoError(e.to_string())).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, "|---|---|---|---|---|") + .map_err(|e| Error::StdIoError(e.to_string())) + .map_err(|e| Error::StdIoError(e.to_string()))?; } else { writeln!(out, "| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |").map_err(|e| Error::StdIoError(e.to_string()))?; - writeln!(out, "|---|---|---|---|---|---|").map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, "|---|---|---|---|---|---|") + .map_err(|e| Error::StdIoError(e.to_string()))?; } for attr in &attributes { @@ -453,11 +485,13 @@ impl<'a> AttributeTableView<'a> { write!( out, "\nThe following attributes can be important for making sampling decisions " - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; write!( out, "and SHOULD be provided **at span creation time** (if provided at all):\n\n" - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; for a in sampling_relevant { write!(out, "* ").map_err(|e| Error::StdIoError(e.to_string()))?; a.write_name_with_optional_link(out, attribute_registry_base_url)?; @@ -529,7 +563,8 @@ impl<'a> MetricView<'a> { "{} {}", &self.group.brief, ctx.add_note(self.group.note.clone()) - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; } Ok(()) } @@ -550,17 +585,20 @@ impl<'a> MetricView<'a> { writeln!( out, "| Name | Instrument Type | Unit (UCUM) | Description | Stability |" - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; writeln!( out, "| -------- | --------------- | ----------- | -------------- | --------- |" - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; write!( out, "| `{}` | {} | `", self.metric_name(), self.instrument() - ).map_err(|e| Error::StdIoError(e.to_string()))?; + ) + .map_err(|e| Error::StdIoError(e.to_string()))?; self.write_unit(out)?; write!(out, "` | ").map_err(|e| Error::StdIoError(e.to_string()))?; self.write_description(out, ctx)?; diff --git a/crates/weaver_semconv_gen/src/lib.rs b/crates/weaver_semconv_gen/src/lib.rs index 07a02105..db945860 100644 --- a/crates/weaver_semconv_gen/src/lib.rs +++ b/crates/weaver_semconv_gen/src/lib.rs @@ -4,8 +4,8 @@ //! The entire crate is a rush job to catch feature parity w/ existing python tooling by //! poorly porting the code into RUST. We expect to optimise and improve things over time. -use std::fs; use miette::Diagnostic; +use std::fs; use serde::Serialize; use weaver_cache::Cache; @@ -242,7 +242,9 @@ pub fn update_markdown( dry_run: bool, attribute_registry_base_url: Option<&str>, ) -> Result<(), Error> { - let original_markdown = fs::read_to_string(file).map_err(|e| Error::StdIoError(e.to_string()))?.replace("\r\n", "\n"); + let original_markdown = fs::read_to_string(file) + .map_err(|e| Error::StdIoError(e.to_string()))? + .replace("\r\n", "\n"); let updated_markdown = update_markdown_contents(&original_markdown, generator, attribute_registry_base_url)?; if !dry_run { diff --git a/deny.toml b/deny.toml index 5d1a8965..45a1822f 100644 --- a/deny.toml +++ b/deny.toml @@ -50,14 +50,14 @@ allow = [ "CC0-1.0", "ISC", "OpenSSL", - "zlib-acknowledgement", +# "zlib-acknowledgement", "Zlib", ] -exceptions = [ - { allow = [ - "Unlicense", - ], name = "measure_time" }, -] +#exceptions = [ +# { allow = [ +# "Unlicense", +# ], name = "measure_time" }, +#] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. diff --git a/src/main.rs b/src/main.rs index 586036f3..9411e05c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,15 +28,15 @@ fn main() { let elapsed = start.elapsed(); println!("Total execution time: {:?}s", elapsed.as_secs_f64()); + + #[allow(clippy::exit)] // Exit the process with a specific error code. std::process::exit(error_code); } #[cfg(not(tarpaulin_include))] fn run_command(cli: &Cli, log: impl Logger + Sync + Clone) -> i32 { match &cli.command { - Some(Commands::Registry(params)) => { - semconv_registry(log, params) - } - None => { 0 } + Some(Commands::Registry(params)) => semconv_registry(log, params), + None => 0, } -} \ No newline at end of file +} diff --git a/src/registry/check.rs b/src/registry/check.rs index c5cc30e7..cf25a24d 100644 --- a/src/registry/check.rs +++ b/src/registry/check.rs @@ -2,7 +2,10 @@ //! Check a semantic convention registry. -use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, RegistryPath, semconv_registry_path_from}; +use crate::registry::{ + check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, + RegistryPath, +}; use clap::Args; use std::path::PathBuf; use weaver_cache::Cache; @@ -48,22 +51,19 @@ pub struct RegistryCheckArgs { /// Check a semantic convention registry. #[cfg(not(tarpaulin_include))] -pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: &RegistryCheckArgs) -> Result<(), DiagnosticMessages> { +pub(crate) fn command( + logger: impl Logger + Sync + Clone, + cache: &Cache, + args: &RegistryCheckArgs, +) -> Result<(), DiagnosticMessages> { logger.loading(&format!("Checking registry `{}`", args.registry)); let registry_id = "default"; - let registry_path = semconv_registry_path_from( - &args.registry, - &args.registry_git_sub_dir, - ); + let registry_path = semconv_registry_path_from(&args.registry, &args.registry_git_sub_dir); // Load the semantic convention registry into a local cache. // No parsing errors should be observed. - let semconv_specs = load_semconv_specs( - ®istry_path, - cache, - logger.clone(), - )?; + let semconv_specs = load_semconv_specs(®istry_path, cache, logger.clone())?; check_policies( ®istry_path, diff --git a/src/registry/generate.rs b/src/registry/generate.rs index c5035807..33a99cbc 100644 --- a/src/registry/generate.rs +++ b/src/registry/generate.rs @@ -13,7 +13,10 @@ use weaver_forge::registry::TemplateRegistry; use weaver_forge::{GeneratorConfig, TemplateEngine}; use weaver_semconv::registry::SemConvRegistry; -use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, RegistryPath, semconv_registry_path_from}; +use crate::registry::{ + check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, + RegistryPath, +}; /// Parameters for the `registry generate` sub-command #[derive(Debug, Args)] @@ -63,17 +66,10 @@ pub(crate) fn command( )); let registry_id = "default"; - let registry_path = semconv_registry_path_from( - &args.registry, - &args.registry_git_sub_dir, - ); + let registry_path = semconv_registry_path_from(&args.registry, &args.registry_git_sub_dir); // Load the semantic convention registry into a local cache. - let semconv_specs = load_semconv_specs( - ®istry_path, - cache, - logger.clone(), - )?; + let semconv_specs = load_semconv_specs(®istry_path, cache, logger.clone())?; check_policies( ®istry_path, @@ -95,8 +91,7 @@ pub(crate) fn command( schema.catalog(), )?; - engine - .generate(logger.clone(), &template_registry, args.output.as_path())?; + engine.generate(logger.clone(), &template_registry, args.output.as_path())?; logger.success("Artifacts generated successfully"); Ok(()) diff --git a/src/registry/mod.rs b/src/registry/mod.rs index f59be616..ca931e9f 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -12,10 +12,10 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use check::RegistryCheckArgs; use weaver_cache::Cache; -use weaver_checker::{Engine, Error, PolicyPackage}; use weaver_checker::Error::{InvalidPolicyFile, PolicyViolation}; +use weaver_checker::{Engine, Error, PolicyPackage}; use weaver_common::diagnostic::DiagnosticMessages; -use weaver_common::error::{ExitIfError, handle_errors}; +use weaver_common::error::{handle_errors, ExitIfError}; use weaver_common::Logger; use weaver_forge::{GeneratorConfig, TemplateEngine}; use weaver_resolved_schema::ResolvedTelemetrySchema; @@ -101,9 +101,9 @@ impl Display for RegistryPath { pub struct RegistryArgs { /// Local path or Git URL of the semantic convention registry. #[arg( - short = 'r', - long, - default_value = "https://github.com/open-telemetry/semantic-conventions.git" + short = 'r', + long, + default_value = "https://github.com/open-telemetry/semantic-conventions.git" )] pub registry: RegistryPath, @@ -123,39 +123,45 @@ pub fn semconv_registry(log: impl Logger + Sync + Clone, command: &RegistryComma }); match &command.command { - RegistrySubCommand::Check(args) => { - write_diagnostics( - check::command(log.clone(), &cache, args), - args.templates.clone(), args.output.clone(), args.target.clone(), log.clone()) - }, - RegistrySubCommand::Generate(args) => { - write_diagnostics( - generate::command(log.clone(), &cache, args), - args.templates.clone(), args.output.clone(), args.target.clone(), log.clone()) - } + RegistrySubCommand::Check(args) => write_diagnostics( + check::command(log.clone(), &cache, args), + args.templates.clone(), + args.output.clone(), + args.target.clone(), + log.clone(), + ), + RegistrySubCommand::Generate(args) => write_diagnostics( + generate::command(log.clone(), &cache, args), + args.templates.clone(), + args.output.clone(), + args.target.clone(), + log.clone(), + ), RegistrySubCommand::Stats(args) => { stats::command(log, &cache, args); 0 - }, + } RegistrySubCommand::Resolve(args) => { resolve::command(log, &cache, args); 0 - }, + } RegistrySubCommand::Search(_) => { unimplemented!() } RegistrySubCommand::UpdateMarkdown(args) => { update_markdown::command(log, &cache, args); 0 - }, + } } } fn write_diagnostics( - result: Result<(),DiagnosticMessages>, + result: Result<(), DiagnosticMessages>, template_root: PathBuf, output: PathBuf, - target: String,logger: impl Logger + Sync + Clone) -> i32 { + target: String, + logger: impl Logger + Sync + Clone, +) -> i32 { if let Err(e) = result { let config = GeneratorConfig::new(template_root); let engine = TemplateEngine::try_new(&format!("errors/{}", target), config) @@ -201,8 +207,7 @@ pub(crate) fn load_semconv_specs( cache: &Cache, log: impl Logger + Sync + Clone, ) -> Result, weaver_resolver::Error> { - let semconv_specs = - SchemaResolver::load_semconv_specs(®istry_path, cache)?; + let semconv_specs = SchemaResolver::load_semconv_specs(registry_path, cache)?; log.success(&format!( "SemConv registry loaded ({} files)", semconv_specs.len() diff --git a/src/registry/resolve.rs b/src/registry/resolve.rs index 3fcd1c23..5de8e6b1 100644 --- a/src/registry/resolve.rs +++ b/src/registry/resolve.rs @@ -13,7 +13,9 @@ use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; use weaver_semconv::registry::SemConvRegistry; -use crate::registry::{load_semconv_specs, resolve_semconv_specs, RegistryArgs, semconv_registry_path_from}; +use crate::registry::{ + load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, RegistryArgs, +}; /// Supported output formats for the resolved schema #[derive(Debug, Clone, ValueEnum)] @@ -65,17 +67,12 @@ pub(crate) fn command( logger.loading(&format!("Resolving registry `{}`", args.registry.registry)); let registry_id = "default"; - let registry_path = semconv_registry_path_from( - &args.registry.registry, - &args.registry.registry_git_sub_dir - ); + let registry_path = + semconv_registry_path_from(&args.registry.registry, &args.registry.registry_git_sub_dir); // Load the semantic convention registry into a local cache. - let semconv_specs = load_semconv_specs( - ®istry_path, - cache, - logger.clone(), - ).exit_if_error(logger.clone()); + let semconv_specs = + load_semconv_specs(®istry_path, cache, logger.clone()).exit_if_error(logger.clone()); let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); let schema = resolve_semconv_specs(&mut registry, logger.clone()); diff --git a/src/registry/stats.rs b/src/registry/stats.rs index 2a22e983..e1b6bac4 100644 --- a/src/registry/stats.rs +++ b/src/registry/stats.rs @@ -2,7 +2,9 @@ //! Compute stats on a semantic convention registry. -use crate::registry::{load_semconv_specs, resolve_semconv_specs, RegistryArgs, semconv_registry_path_from}; +use crate::registry::{ + load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, RegistryArgs, +}; use clap::Args; use weaver_cache::Cache; use weaver_common::error::ExitIfError; @@ -29,17 +31,12 @@ pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: & )); let registry_id = "default"; - let registry_path = semconv_registry_path_from( - &args.registry.registry, - &args.registry.registry_git_sub_dir, - ); + let registry_path = + semconv_registry_path_from(&args.registry.registry, &args.registry.registry_git_sub_dir); // Load the semantic convention registry into a local cache. - let semconv_specs = load_semconv_specs( - ®istry_path, - cache, - logger.clone(), - ).exit_if_error(logger.clone()); + let semconv_specs = + load_semconv_specs(®istry_path, cache, logger.clone()).exit_if_error(logger.clone()); let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); display_semconv_registry_stats(®istry); From c02adc116651e4bf89c107b705875e7d8ab62693 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 14 May 2024 17:46:11 -0700 Subject: [PATCH 05/17] chore(forge): Improve test coverage --- .../weaver_forge/src/extensions/ansi_code.rs | 220 ++++++++++++++++++ crates/weaver_forge/src/lib.rs | 45 +--- 2 files changed, 222 insertions(+), 43 deletions(-) diff --git a/crates/weaver_forge/src/extensions/ansi_code.rs b/crates/weaver_forge/src/extensions/ansi_code.rs index e2b7354f..14454ab4 100644 --- a/crates/weaver_forge/src/extensions/ansi_code.rs +++ b/crates/weaver_forge/src/extensions/ansi_code.rs @@ -219,3 +219,223 @@ pub(crate) fn underline(input: &Value) -> String { pub(crate) fn strikethrough(input: &Value) -> String { format!("\x1b[9m{}\x1b[0m", input) } + +/// Adds all the ANSI filters to the given environment. +pub(crate) fn add_ansi_filters(env: &mut minijinja::Environment<'_>) { + env.add_filter("black", black); + env.add_filter("red", red); + env.add_filter("green", green); + env.add_filter("yellow", yellow); + env.add_filter("blue", blue); + env.add_filter("magenta", magenta); + env.add_filter("cyan", cyan); + env.add_filter("white", white); + + env.add_filter("bright_black", bright_black); + env.add_filter("bright_red", bright_red); + env.add_filter("bright_green", bright_green); + env.add_filter("bright_yellow", bright_yellow); + env.add_filter("bright_blue", bright_blue); + env.add_filter("bright_magenta", bright_magenta); + env.add_filter("bright_cyan", bright_cyan); + env.add_filter("bright_white", bright_white); + + env.add_filter("bg_black", bg_black); + env.add_filter("bg_red", bg_red); + env.add_filter("bg_green", bg_green); + env.add_filter("bg_yellow", bg_yellow); + env.add_filter("bg_blue", bg_blue); + env.add_filter("bg_magenta", bg_magenta); + env.add_filter("bg_cyan", bg_cyan); + env.add_filter("bg_white", bg_white); + + env.add_filter("bg_bright_black", bg_bright_black); + env.add_filter("bg_bright_red", bg_bright_red); + env.add_filter("bg_bright_green", bg_bright_green); + env.add_filter("bg_bright_yellow", bg_bright_yellow); + env.add_filter("bg_bright_blue", bg_bright_blue); + env.add_filter("bg_bright_magenta", bg_bright_magenta); + env.add_filter("bg_bright_cyan", bg_bright_cyan); + env.add_filter("bg_bright_white", bg_bright_white); + + env.add_filter("bold", bold); + env.add_filter("italic", italic); + env.add_filter("underline", underline); + env.add_filter("strikethrough", strikethrough); +} + +#[cfg(test)] +mod tests { + use crate::extensions::ansi_code::add_ansi_filters; + use minijinja::Environment; + + #[test] + fn test_ansi_filters() { + let mut env = Environment::new(); + let ctx = serde_json::Value::Null; + + add_ansi_filters(&mut env); + + assert_eq!( + env.render_str("{{ 'test' | black }}", &ctx).unwrap(), + "\x1b[30mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | red }}", &ctx).unwrap(), + "\x1b[31mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | green }}", &ctx).unwrap(), + "\x1b[32mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | yellow }}", &ctx).unwrap(), + "\x1b[33mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | blue }}", &ctx).unwrap(), + "\x1b[34mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | magenta }}", &ctx).unwrap(), + "\x1b[35mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | cyan }}", &ctx).unwrap(), + "\x1b[36mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | white }}", &ctx).unwrap(), + "\x1b[37mtest\x1b[0m" + ); + + assert_eq!( + env.render_str("{{ 'test' | bright_black }}", &ctx).unwrap(), + "\x1b[90mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bright_red }}", &ctx).unwrap(), + "\x1b[91mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bright_green }}", &ctx).unwrap(), + "\x1b[92mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bright_yellow }}", &ctx) + .unwrap(), + "\x1b[93mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bright_blue }}", &ctx).unwrap(), + "\x1b[94mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bright_magenta }}", &ctx) + .unwrap(), + "\x1b[95mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bright_cyan }}", &ctx).unwrap(), + "\x1b[96mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bright_white }}", &ctx).unwrap(), + "\x1b[97mtest\x1b[0m" + ); + + assert_eq!( + env.render_str("{{ 'test' | bg_black }}", &ctx).unwrap(), + "\x1b[40mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_red }}", &ctx).unwrap(), + "\x1b[41mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_green }}", &ctx).unwrap(), + "\x1b[42mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_yellow }}", &ctx).unwrap(), + "\x1b[43mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_blue }}", &ctx).unwrap(), + "\x1b[44mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_magenta }}", &ctx).unwrap(), + "\x1b[45mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_cyan }}", &ctx).unwrap(), + "\x1b[46mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_white }}", &ctx).unwrap(), + "\x1b[47mtest\x1b[0m" + ); + + assert_eq!( + env.render_str("{{ 'test' | bg_bright_black }}", &ctx) + .unwrap(), + "\x1b[100mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_bright_red }}", &ctx) + .unwrap(), + "\x1b[101mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_bright_green }}", &ctx) + .unwrap(), + "\x1b[102mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_bright_yellow }}", &ctx) + .unwrap(), + "\x1b[103mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_bright_blue }}", &ctx) + .unwrap(), + "\x1b[104mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_bright_magenta }}", &ctx) + .unwrap(), + "\x1b[105mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_bright_cyan }}", &ctx) + .unwrap(), + "\x1b[106mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | bg_bright_white }}", &ctx) + .unwrap(), + "\x1b[107mtest\x1b[0m" + ); + + assert_eq!( + env.render_str("{{ 'test' | bold }}", &ctx).unwrap(), + "\x1b[1mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | italic }}", &ctx).unwrap(), + "\x1b[3mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | underline }}", &ctx).unwrap(), + "\x1b[4mtest\x1b[0m" + ); + assert_eq!( + env.render_str("{{ 'test' | strikethrough }}", &ctx) + .unwrap(), + "\x1b[9mtest\x1b[0m" + ); + + assert_eq!(env.render_str("{{ 'test' | black | bg_white | bold | italic | underline | strikethrough }}", &ctx).unwrap(), "\u{1b}[9m\u{1b}[4m\u{1b}[3m\u{1b}[1m\u{1b}[47m\u{1b}[30mtest\u{1b}[0m\u{1b}[0m\u{1b}[0m\u{1b}[0m\u{1b}[0m\u{1b}[0m"); + } +} diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index 4c04652b..1aafbc4a 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -28,6 +28,7 @@ use crate::config::{ApplicationMode, CaseConvention, TargetConfig}; use crate::debug::error_summary; use crate::error::Error::InvalidConfigFile; use crate::extensions::acronym::acronym; +use crate::extensions::ansi_code::add_ansi_filters; use crate::extensions::case_converter::case_converter; use crate::extensions::code; use crate::registry::{TemplateGroup, TemplateRegistry}; @@ -507,49 +508,7 @@ impl TemplateEngine { env.add_filter("required", extensions::otel::required); env.add_filter("not_required", extensions::otel::not_required); - env.add_filter("black", extensions::ansi_code::black); - env.add_filter("red", extensions::ansi_code::red); - env.add_filter("green", extensions::ansi_code::green); - env.add_filter("yellow", extensions::ansi_code::yellow); - env.add_filter("blue", extensions::ansi_code::blue); - env.add_filter("magenta", extensions::ansi_code::magenta); - env.add_filter("cyan", extensions::ansi_code::cyan); - env.add_filter("white", extensions::ansi_code::white); - - env.add_filter("bright_black", extensions::ansi_code::bright_black); - env.add_filter("bright_red", extensions::ansi_code::bright_red); - env.add_filter("bright_green", extensions::ansi_code::bright_green); - env.add_filter("bright_yellow", extensions::ansi_code::bright_yellow); - env.add_filter("bright_blue", extensions::ansi_code::bright_blue); - env.add_filter("bright_magenta", extensions::ansi_code::bright_magenta); - env.add_filter("bright_cyan", extensions::ansi_code::bright_cyan); - env.add_filter("bright_white", extensions::ansi_code::bright_white); - - env.add_filter("bg_black", extensions::ansi_code::bg_black); - env.add_filter("bg_red", extensions::ansi_code::bg_red); - env.add_filter("bg_green", extensions::ansi_code::bg_green); - env.add_filter("bg_yellow", extensions::ansi_code::bg_yellow); - env.add_filter("bg_blue", extensions::ansi_code::bg_blue); - env.add_filter("bg_magenta", extensions::ansi_code::bg_magenta); - env.add_filter("bg_cyan", extensions::ansi_code::bg_cyan); - env.add_filter("bg_white", extensions::ansi_code::bg_white); - - env.add_filter("bg_bright_black", extensions::ansi_code::bg_bright_black); - env.add_filter("bg_bright_red", extensions::ansi_code::bg_bright_red); - env.add_filter("bg_bright_green", extensions::ansi_code::bg_bright_green); - env.add_filter("bg_bright_yellow", extensions::ansi_code::bg_bright_yellow); - env.add_filter("bg_bright_blue", extensions::ansi_code::bg_bright_blue); - env.add_filter( - "bg_bright_magenta", - extensions::ansi_code::bg_bright_magenta, - ); - env.add_filter("bg_bright_cyan", extensions::ansi_code::bg_bright_cyan); - env.add_filter("bg_bright_white", extensions::ansi_code::bg_bright_white); - - env.add_filter("bold", extensions::ansi_code::bold); - env.add_filter("italic", extensions::ansi_code::italic); - env.add_filter("underline", extensions::ansi_code::underline); - env.add_filter("strikethrough", extensions::ansi_code::strikethrough); + add_ansi_filters(&mut env); // ToDo Implement more filters: stable, experimental, deprecated env.add_test("stable", extensions::otel::is_stable); From ba7dc9ce0412885d3f4be7fabc6dbce79787c05b Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 14 May 2024 17:58:11 -0700 Subject: [PATCH 06/17] chore(common): Improve test coverage --- crates/weaver_common/src/diagnostic.rs | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/crates/weaver_common/src/diagnostic.rs b/crates/weaver_common/src/diagnostic.rs index d1a9007c..e8eceb5f 100644 --- a/crates/weaver_common/src/diagnostic.rs +++ b/crates/weaver_common/src/diagnostic.rs @@ -102,3 +102,46 @@ impl From< Self(vec![DiagnosticMessage::new(error)]) } } + +#[cfg(test)] +mod tests { + use super::*; + use miette::Diagnostic; + + #[derive(thiserror::Error, Debug, Clone, Diagnostic, Serialize)] + #[error("This is a test error")] + #[diagnostic(code(test::error))] + #[diagnostic(url = "https://example.com")] + #[diagnostic(help = "This is a test error")] + #[diagnostic(severity = "error")] + struct TestError { + message: String, + } + + #[test] + fn test_diagnostic_message() { + let error = TestError { + message: "This is a test error".to_string(), + }; + let diagnostic_message = DiagnosticMessage::new(error); + assert_eq!(diagnostic_message.diagnostic.message, "This is a test error"); + assert_eq!(diagnostic_message.diagnostic.code, Some("test::error".to_string())); + assert_eq!(diagnostic_message.diagnostic.severity, Some(Severity::Error)); + assert_eq!(diagnostic_message.diagnostic.help, Some("This is a test error".to_string())); + assert_eq!(diagnostic_message.diagnostic.url, Some("https://example.com".to_string())); + } + + #[test] + fn test_diagnostic_messages() { + let error = TestError { + message: "This is a test error".to_string(), + }; + let diagnostic_messages = DiagnosticMessages::from_error(error); + assert_eq!(diagnostic_messages.0.len(), 1); + assert_eq!(diagnostic_messages.0[0].diagnostic.message, "This is a test error"); + assert_eq!(diagnostic_messages.0[0].diagnostic.code, Some("test::error".to_string())); + assert_eq!(diagnostic_messages.0[0].diagnostic.severity, Some(Severity::Error)); + assert_eq!(diagnostic_messages.0[0].diagnostic.help, Some("This is a test error".to_string())); + assert_eq!(diagnostic_messages.0[0].diagnostic.url, Some("https://example.com".to_string())); + } +} \ No newline at end of file From a8a6333cb1b3bd6afe75420148d3b2d3618c6898 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 14 May 2024 22:44:08 -0700 Subject: [PATCH 07/17] chore(common): Improve test coverage --- crates/weaver_common/src/diagnostic.rs | 79 +++++++++++++++++++++----- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/crates/weaver_common/src/diagnostic.rs b/crates/weaver_common/src/diagnostic.rs index e8eceb5f..c3e2d777 100644 --- a/crates/weaver_common/src/diagnostic.rs +++ b/crates/weaver_common/src/diagnostic.rs @@ -106,7 +106,7 @@ impl From< #[cfg(test)] mod tests { use super::*; - use miette::Diagnostic; + use miette::{Diagnostic, diagnostic}; #[derive(thiserror::Error, Debug, Clone, Diagnostic, Serialize)] #[error("This is a test error")] @@ -124,11 +124,26 @@ mod tests { message: "This is a test error".to_string(), }; let diagnostic_message = DiagnosticMessage::new(error); - assert_eq!(diagnostic_message.diagnostic.message, "This is a test error"); - assert_eq!(diagnostic_message.diagnostic.code, Some("test::error".to_string())); - assert_eq!(diagnostic_message.diagnostic.severity, Some(Severity::Error)); - assert_eq!(diagnostic_message.diagnostic.help, Some("This is a test error".to_string())); - assert_eq!(diagnostic_message.diagnostic.url, Some("https://example.com".to_string())); + assert_eq!( + diagnostic_message.diagnostic.message, + "This is a test error" + ); + assert_eq!( + diagnostic_message.diagnostic.code, + Some("test::error".to_string()) + ); + assert_eq!( + diagnostic_message.diagnostic.severity, + Some(Severity::Error) + ); + assert_eq!( + diagnostic_message.diagnostic.help, + Some("This is a test error".to_string()) + ); + assert_eq!( + diagnostic_message.diagnostic.url, + Some("https://example.com".to_string()) + ); } #[test] @@ -136,12 +151,50 @@ mod tests { let error = TestError { message: "This is a test error".to_string(), }; - let diagnostic_messages = DiagnosticMessages::from_error(error); + let diagnostic_messages = DiagnosticMessages::from_error(error.clone()); assert_eq!(diagnostic_messages.0.len(), 1); - assert_eq!(diagnostic_messages.0[0].diagnostic.message, "This is a test error"); - assert_eq!(diagnostic_messages.0[0].diagnostic.code, Some("test::error".to_string())); - assert_eq!(diagnostic_messages.0[0].diagnostic.severity, Some(Severity::Error)); - assert_eq!(diagnostic_messages.0[0].diagnostic.help, Some("This is a test error".to_string())); - assert_eq!(diagnostic_messages.0[0].diagnostic.url, Some("https://example.com".to_string())); + assert_eq!( + diagnostic_messages.0[0].diagnostic.message, + "This is a test error" + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.code, + Some("test::error".to_string()) + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.severity, + Some(Severity::Error) + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.help, + Some("This is a test error".to_string()) + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.url, + Some("https://example.com".to_string()) + ); + + let diagnostic_messages = DiagnosticMessages::from_errors(vec![error]); + assert_eq!(diagnostic_messages.0.len(), 1); + assert_eq!( + diagnostic_messages.0[0].diagnostic.message, + "This is a test error" + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.code, + Some("test::error".to_string()) + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.severity, + Some(Severity::Error) + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.help, + Some("This is a test error".to_string()) + ); + assert_eq!( + diagnostic_messages.0[0].diagnostic.url, + Some("https://example.com".to_string()) + ); } -} \ No newline at end of file +} From d0bfca79746039eb621a4626a5576a3402c8aba3 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Wed, 15 May 2024 14:13:30 -0700 Subject: [PATCH 08/17] chore(common): Implement --diagnostic-format --- crates/weaver_common/src/diagnostic.rs | 22 +++++----- src/main.rs | 8 ++-- src/registry/check.rs | 23 +++-------- src/registry/mod.rs | 57 +++++++++++++++----------- src/registry/update_markdown.rs | 13 +++--- 5 files changed, 61 insertions(+), 62 deletions(-) diff --git a/crates/weaver_common/src/diagnostic.rs b/crates/weaver_common/src/diagnostic.rs index c3e2d777..f37bd9ed 100644 --- a/crates/weaver_common/src/diagnostic.rs +++ b/crates/weaver_common/src/diagnostic.rs @@ -121,7 +121,7 @@ mod tests { #[test] fn test_diagnostic_message() { let error = TestError { - message: "This is a test error".to_string(), + message: "This is a test error".to_owned(), }; let diagnostic_message = DiagnosticMessage::new(error); assert_eq!( @@ -130,7 +130,7 @@ mod tests { ); assert_eq!( diagnostic_message.diagnostic.code, - Some("test::error".to_string()) + Some("test::error".to_owned()) ); assert_eq!( diagnostic_message.diagnostic.severity, @@ -138,18 +138,18 @@ mod tests { ); assert_eq!( diagnostic_message.diagnostic.help, - Some("This is a test error".to_string()) + Some("This is a test error".to_owned()) ); assert_eq!( diagnostic_message.diagnostic.url, - Some("https://example.com".to_string()) + Some("https://example.com".to_owned()) ); } #[test] fn test_diagnostic_messages() { let error = TestError { - message: "This is a test error".to_string(), + message: "This is a test error".to_owned(), }; let diagnostic_messages = DiagnosticMessages::from_error(error.clone()); assert_eq!(diagnostic_messages.0.len(), 1); @@ -159,7 +159,7 @@ mod tests { ); assert_eq!( diagnostic_messages.0[0].diagnostic.code, - Some("test::error".to_string()) + Some("test::error".to_owned()) ); assert_eq!( diagnostic_messages.0[0].diagnostic.severity, @@ -167,11 +167,11 @@ mod tests { ); assert_eq!( diagnostic_messages.0[0].diagnostic.help, - Some("This is a test error".to_string()) + Some("This is a test error".to_owned()) ); assert_eq!( diagnostic_messages.0[0].diagnostic.url, - Some("https://example.com".to_string()) + Some("https://example.com".to_owned()) ); let diagnostic_messages = DiagnosticMessages::from_errors(vec![error]); @@ -182,7 +182,7 @@ mod tests { ); assert_eq!( diagnostic_messages.0[0].diagnostic.code, - Some("test::error".to_string()) + Some("test::error".to_owned()) ); assert_eq!( diagnostic_messages.0[0].diagnostic.severity, @@ -190,11 +190,11 @@ mod tests { ); assert_eq!( diagnostic_messages.0[0].diagnostic.help, - Some("This is a test error".to_string()) + Some("This is a test error".to_owned()) ); assert_eq!( diagnostic_messages.0[0].diagnostic.url, - Some("https://example.com".to_string()) + Some("https://example.com".to_owned()) ); } } diff --git a/src/main.rs b/src/main.rs index 9411e05c..239f590c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ fn main() { let cli = Cli::parse(); let start = std::time::Instant::now(); - let error_code = if cli.quiet { + let exit_code = if cli.quiet { let log = QuietLogger::new(); run_command(&cli, log) } else { @@ -29,10 +29,12 @@ fn main() { let elapsed = start.elapsed(); println!("Total execution time: {:?}s", elapsed.as_secs_f64()); - #[allow(clippy::exit)] // Exit the process with a specific error code. - std::process::exit(error_code); + // Exit the process with a specific exit code. + #[allow(clippy::exit)] + std::process::exit(exit_code); } +/// Run the command specified by the CLI arguments and return the exit code. #[cfg(not(tarpaulin_include))] fn run_command(cli: &Cli, log: impl Logger + Sync + Clone) -> i32 { match &cli.command { diff --git a/src/registry/check.rs b/src/registry/check.rs index cf25a24d..f31e6c8e 100644 --- a/src/registry/check.rs +++ b/src/registry/check.rs @@ -2,10 +2,7 @@ //! Check a semantic convention registry. -use crate::registry::{ - check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, - RegistryPath, -}; +use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, RegistryPath, RegistryArgs, DiagnosticArgs}; use clap::Args; use std::path::PathBuf; use weaver_cache::Cache; @@ -16,20 +13,6 @@ use weaver_semconv::registry::SemConvRegistry; /// Parameters for the `registry check` sub-command #[derive(Debug, Args)] pub struct RegistryCheckArgs { - /// Target to generate the artifacts for. - #[arg(default_value = "console")] - pub target: String, - - /// Path to the directory where the generated artifacts will be saved. - /// Default is the `output` directory. - #[arg(default_value = "output")] - pub output: PathBuf, - - /// Path to the directory where the templates are located. - /// Default is the `templates` directory. - #[arg(short = 't', long, default_value = "templates")] - pub templates: PathBuf, - /// Local path or Git URL of the semantic convention registry to check. #[arg( short = 'r', @@ -47,6 +30,10 @@ pub struct RegistryCheckArgs { /// convention registry. #[arg(short = 'p', long)] pub policies: Vec, + + /// Parameters to specify the diagnostic format. + #[command(flatten)] + pub diagnostic: DiagnosticArgs, } /// Check a semantic convention registry. diff --git a/src/registry/mod.rs b/src/registry/mod.rs index ca931e9f..3ab1624e 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -4,7 +4,6 @@ use std::fmt::Display; use std::path::PathBuf; -use std::process::exit; use std::str::FromStr; use clap::{Args, Subcommand}; @@ -12,10 +11,10 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use check::RegistryCheckArgs; use weaver_cache::Cache; -use weaver_checker::Error::{InvalidPolicyFile, PolicyViolation}; use weaver_checker::{Engine, Error, PolicyPackage}; +use weaver_checker::Error::{InvalidPolicyFile, PolicyViolation}; use weaver_common::diagnostic::DiagnosticMessages; -use weaver_common::error::{handle_errors, ExitIfError}; +use weaver_common::error::{ExitIfError, handle_errors}; use weaver_common::Logger; use weaver_forge::{GeneratorConfig, TemplateEngine}; use weaver_resolved_schema::ResolvedTelemetrySchema; @@ -113,28 +112,39 @@ pub struct RegistryArgs { pub registry_git_sub_dir: Option, } -/// Manage a semantic convention registry. +/// Set of parameters used to specify the diagnostic format. +#[derive(Args, Debug)] +pub struct DiagnosticArgs { + /// Format used to render the diagnostic messages. Predefined formats are: ansi (default), json, + /// gh_workflow_command. + #[arg( + short = 'f', + long, + default_value = "ansi" + )] + pub diagnostic_format: String, +} + +/// Manage a semantic convention registry and return the exit code. #[cfg(not(tarpaulin_include))] pub fn semconv_registry(log: impl Logger + Sync + Clone, command: &RegistryCommand) -> i32 { - let cache = Cache::try_new().unwrap_or_else(|e| { - log.error(&e.to_string()); - #[allow(clippy::exit)] // Expected behavior - exit(1); - }); + let cache = match Cache::try_new() { + Ok(cache) => cache, + Err(e) => { + log.error(&format!("Failed to create cache: {}", e)); + return 1; + } + }; match &command.command { RegistrySubCommand::Check(args) => write_diagnostics( check::command(log.clone(), &cache, args), - args.templates.clone(), - args.output.clone(), - args.target.clone(), + args.diagnostic.clone(), log.clone(), ), RegistrySubCommand::Generate(args) => write_diagnostics( generate::command(log.clone(), &cache, args), - args.templates.clone(), - args.output.clone(), - args.target.clone(), + args.diagnostic.clone(), log.clone(), ), RegistrySubCommand::Stats(args) => { @@ -145,21 +155,18 @@ pub fn semconv_registry(log: impl Logger + Sync + Clone, command: &RegistryComma resolve::command(log, &cache, args); 0 } - RegistrySubCommand::Search(_) => { - unimplemented!() - } - RegistrySubCommand::UpdateMarkdown(args) => { - update_markdown::command(log, &cache, args); - 0 - } + RegistrySubCommand::Search(_) => unimplemented!(), + RegistrySubCommand::UpdateMarkdown(args) => write_diagnostics( + update_markdown::command(log.clone(), &cache, args), + args.diagnostic.clone(), + log.clone(), + ), } } fn write_diagnostics( result: Result<(), DiagnosticMessages>, - template_root: PathBuf, - output: PathBuf, - target: String, + diagnostic: DiagnosticArgs, logger: impl Logger + Sync + Clone, ) -> i32 { if let Err(e) = result { diff --git a/src/registry/update_markdown.rs b/src/registry/update_markdown.rs index 9a9248f9..7b09d00a 100644 --- a/src/registry/update_markdown.rs +++ b/src/registry/update_markdown.rs @@ -6,6 +6,7 @@ use crate::registry::{semconv_registry_path_from, RegistryPath}; use clap::Args; use weaver_cache::Cache; +use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::error::ExitIfError; use weaver_common::Logger; use weaver_forge::{GeneratorConfig, TemplateEngine}; @@ -58,17 +59,17 @@ pub(crate) fn command( log: impl Logger + Sync + Clone, cache: &Cache, args: &RegistryUpdateMarkdownArgs, -) { +) -> Result<(), DiagnosticMessages> { fn is_markdown(entry: &walkdir::DirEntry) -> bool { let path = entry.path(); let extension = path.extension().unwrap_or_else(|| std::ffi::OsStr::new("")); path.is_file() && extension == "md" } // Construct a generator if we were given a `--target` argument. - let generator = args.target.as_ref().map(|target| { - TemplateEngine::try_new(&format!("registry/{}", target), GeneratorConfig::default()) - .exit_if_error(log.clone()) - }); + let generator = match args.target.as_ref() { + None => None, + Some(target) => Some(TemplateEngine::try_new(&format!("registry/{}", target), GeneratorConfig::default())?), + }; let generator = SnippetGenerator::try_from_url( semconv_registry_path_from(&args.registry, &args.registry_git_sub_dir), @@ -104,4 +105,6 @@ pub(crate) fn command( if has_error { panic!("weaver registry update-markdown failed."); } + + Ok(()) } From 50f0933174286718dc5a8659127a41685ee63fcc Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Thu, 16 May 2024 09:53:19 -0700 Subject: [PATCH 09/17] chore(diag): Unify diagnostic message processing and rendering. --- Cargo.lock | 28 ++--- crates/weaver_common/src/diagnostic.rs | 11 +- crates/weaver_common/src/error.rs | 76 ------------- .../ansi}/errors.txt.j2 | 0 .../ansi}/weaver.yaml | 0 .../gh_workflow_command/errors.txt.j2 | 0 .../gh_workflow_command/weaver.yaml | 0 .../json/errors.json.j2 | 0 .../json/weaver.yaml | 0 src/main.rs | 2 +- src/registry/check.rs | 7 +- src/registry/generate.rs | 8 +- src/registry/mod.rs | 100 ++++++++++-------- src/registry/resolve.rs | 18 ++-- src/registry/stats.rs | 21 ++-- src/registry/update_markdown.rs | 15 ++- 16 files changed, 129 insertions(+), 157 deletions(-) rename {templates/errors/console => diagnostic_templates/ansi}/errors.txt.j2 (100%) rename {templates/errors/console => diagnostic_templates/ansi}/weaver.yaml (100%) rename {templates/errors => diagnostic_templates}/gh_workflow_command/errors.txt.j2 (100%) rename {templates/errors => diagnostic_templates}/gh_workflow_command/weaver.yaml (100%) rename {templates/errors => diagnostic_templates}/json/errors.json.j2 (100%) rename {templates/errors => diagnostic_templates}/json/weaver.yaml (100%) diff --git a/Cargo.lock b/Cargo.lock index cd02193b..19262180 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2811,9 +2811,9 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -2878,18 +2878,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -2909,9 +2909,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -3266,9 +3266,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", @@ -3278,18 +3278,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap", "serde", diff --git a/crates/weaver_common/src/diagnostic.rs b/crates/weaver_common/src/diagnostic.rs index f37bd9ed..dd794e19 100644 --- a/crates/weaver_common/src/diagnostic.rs +++ b/crates/weaver_common/src/diagnostic.rs @@ -92,6 +92,15 @@ impl DiagnosticMessages { pub fn from_error(error: M) -> Self { Self(vec![DiagnosticMessage::new(error)]) } + + /// Returns true if any of the diagnostic messages have an error severity + /// level. + #[must_use] + pub fn has_error(&self) -> bool { + self.0 + .iter() + .any(|message| message.diagnostic.severity == Some(Severity::Error)) + } } impl From @@ -106,7 +115,7 @@ impl From< #[cfg(test)] mod tests { use super::*; - use miette::{Diagnostic, diagnostic}; + use miette::{diagnostic, Diagnostic}; #[derive(thiserror::Error, Debug, Clone, Diagnostic, Serialize)] #[error("This is a test error")] diff --git a/crates/weaver_common/src/error.rs b/crates/weaver_common/src/error.rs index 18f705e5..a651979f 100644 --- a/crates/weaver_common/src/error.rs +++ b/crates/weaver_common/src/error.rs @@ -7,7 +7,6 @@ use crate::Logger; use miette::Diagnostic; use serde::Serialize; -use std::process::exit; /// A trait marker for Weaver diagnostic. pub trait WeaverDiagnostic {} @@ -75,35 +74,6 @@ pub trait ExitIfError { /// The contained value if the result is `Ok`. /// Panics if the result is `Err`. fn panic_if_error(self, logger: impl Logger) -> T; - - /// Processes the `Result` and exits the application if it is an `Err`. - /// If `Ok`, the contained value is returned. - /// - /// # Arguments - /// * `self` - The `Result` to process. - /// * `logger` - An object implementing the `Logger` trait used to log any - /// errors. - /// - /// # Returns - /// The contained value if the result is `Ok`. - /// Exits the process if the result is `Err`. - fn exit_if_error(self, logger: impl Logger) -> T; - - /// Processes the `Result` and exits the application with a specified exit - /// code if it is an `Err`. - /// If `Ok`, the contained value is returned. - /// - /// # Arguments - /// * `self` - The `Result` to process. - /// * `code` - The exit code to use if the result is an `Err`. - /// * `logger` - An object implementing the `Logger` trait used to log any - /// errors. - /// - /// # Returns - /// The contained value if the result is `Ok`. - /// Exits the process with the specified `code` if the result is `Err`. - #[allow(dead_code)] - fn exit_with_code_if_error(self, code: i32, logger: impl Logger) -> T; } /// Provides default implementations of the `ExitIfError` trait for any @@ -129,50 +99,4 @@ impl> ExitIfError for Result { } } } - - /// Processes the `Result` and exits the application if it is an `Err`. - /// If `Ok`, the contained value is returned. - /// - /// # Arguments - /// * `self` - The `Result` to process. - /// * `logger` - An object implementing the `Logger` trait used to log any - /// errors. - /// - /// # Returns - /// The contained value if the result is `Ok`. - /// Exits the process if the result is `Err`. - fn exit_if_error(self, logger: impl Logger) -> T { - match self { - Ok(value) => value, - Err(e) => { - e.errors().iter().for_each(|msg| logger.error(msg)); - #[allow(clippy::exit)] // Expected behavior - exit(1) - } - } - } - - /// Processes the `Result` and exits the application with a specified exit - /// code if it is an `Err`. - /// If `Ok`, the contained value is returned. - /// - /// # Arguments - /// * `self` - The `Result` to process. - /// * `code` - The exit code to use if the result is an `Err`. - /// * `logger` - An object implementing the `Logger` trait used to log any - /// errors. - /// - /// # Returns - /// The contained value if the result is `Ok`. - /// Exits the process with the specified `code` if the result is `Err`. - fn exit_with_code_if_error(self, code: i32, logger: impl Logger) -> T { - match self { - Ok(value) => value, - Err(e) => { - e.errors().iter().for_each(|msg| logger.error(msg)); - #[allow(clippy::exit)] // Expected behavior - exit(code) - } - } - } } diff --git a/templates/errors/console/errors.txt.j2 b/diagnostic_templates/ansi/errors.txt.j2 similarity index 100% rename from templates/errors/console/errors.txt.j2 rename to diagnostic_templates/ansi/errors.txt.j2 diff --git a/templates/errors/console/weaver.yaml b/diagnostic_templates/ansi/weaver.yaml similarity index 100% rename from templates/errors/console/weaver.yaml rename to diagnostic_templates/ansi/weaver.yaml diff --git a/templates/errors/gh_workflow_command/errors.txt.j2 b/diagnostic_templates/gh_workflow_command/errors.txt.j2 similarity index 100% rename from templates/errors/gh_workflow_command/errors.txt.j2 rename to diagnostic_templates/gh_workflow_command/errors.txt.j2 diff --git a/templates/errors/gh_workflow_command/weaver.yaml b/diagnostic_templates/gh_workflow_command/weaver.yaml similarity index 100% rename from templates/errors/gh_workflow_command/weaver.yaml rename to diagnostic_templates/gh_workflow_command/weaver.yaml diff --git a/templates/errors/json/errors.json.j2 b/diagnostic_templates/json/errors.json.j2 similarity index 100% rename from templates/errors/json/errors.json.j2 rename to diagnostic_templates/json/errors.json.j2 diff --git a/templates/errors/json/weaver.yaml b/diagnostic_templates/json/weaver.yaml similarity index 100% rename from templates/errors/json/weaver.yaml rename to diagnostic_templates/json/weaver.yaml diff --git a/src/main.rs b/src/main.rs index 239f590c..3b1b7fd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ fn main() { let elapsed = start.elapsed(); println!("Total execution time: {:?}s", elapsed.as_secs_f64()); - // Exit the process with a specific exit code. + // Exit the process with the exit code provided by the `run_command` function. #[allow(clippy::exit)] std::process::exit(exit_code); } diff --git a/src/registry/check.rs b/src/registry/check.rs index f31e6c8e..f2b2055c 100644 --- a/src/registry/check.rs +++ b/src/registry/check.rs @@ -2,7 +2,10 @@ //! Check a semantic convention registry. -use crate::registry::{check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, RegistryPath, RegistryArgs, DiagnosticArgs}; +use crate::registry::{ + check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, + DiagnosticArgs, RegistryPath, +}; use clap::Args; use std::path::PathBuf; use weaver_cache::Cache; @@ -61,7 +64,7 @@ pub(crate) fn command( )?; let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); - _ = resolve_semconv_specs(&mut registry, logger.clone()); + _ = resolve_semconv_specs(&mut registry, logger.clone())?; Ok(()) } diff --git a/src/registry/generate.rs b/src/registry/generate.rs index 33a99cbc..35ad4f5d 100644 --- a/src/registry/generate.rs +++ b/src/registry/generate.rs @@ -15,7 +15,7 @@ use weaver_semconv::registry::SemConvRegistry; use crate::registry::{ check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, - RegistryPath, + DiagnosticArgs, RegistryPath, }; /// Parameters for the `registry generate` sub-command @@ -51,6 +51,10 @@ pub struct RegistryGenerateArgs { /// convention registry before the resolution process. #[arg(short = 'b', long)] pub before_resolution_policies: Vec, + + /// Parameters to specify the diagnostic format. + #[command(flatten)] + pub diagnostic: DiagnosticArgs, } /// Generate artifacts from a semantic convention registry. @@ -79,7 +83,7 @@ pub(crate) fn command( logger.clone(), )?; let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); - let schema = resolve_semconv_specs(&mut registry, logger.clone()); + let schema = resolve_semconv_specs(&mut registry, logger.clone())?; let config = GeneratorConfig::new(args.templates.clone()); let engine = TemplateEngine::try_new(&format!("registry/{}", args.target), config)?; diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 3ab1624e..dd9724dd 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -11,10 +11,10 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use check::RegistryCheckArgs; use weaver_cache::Cache; -use weaver_checker::{Engine, Error, PolicyPackage}; use weaver_checker::Error::{InvalidPolicyFile, PolicyViolation}; +use weaver_checker::{Engine, Error, PolicyPackage}; use weaver_common::diagnostic::DiagnosticMessages; -use weaver_common::error::{ExitIfError, handle_errors}; +use weaver_common::error::handle_errors; use weaver_common::Logger; use weaver_forge::{GeneratorConfig, TemplateEngine}; use weaver_resolved_schema::ResolvedTelemetrySchema; @@ -113,16 +113,16 @@ pub struct RegistryArgs { } /// Set of parameters used to specify the diagnostic format. -#[derive(Args, Debug)] +#[derive(Args, Debug, Clone)] pub struct DiagnosticArgs { - /// Format used to render the diagnostic messages. Predefined formats are: ansi (default), json, + /// Format used to render the diagnostic messages. Predefined formats are: ansi, json, /// gh_workflow_command. - #[arg( - short = 'f', - long, - default_value = "ansi" - )] + #[arg(long, default_value = "ansi")] pub diagnostic_format: String, + + /// Path to the directory where the diagnostic templates are located. + #[arg(long, default_value = "diagnostic_templates")] + pub diagnostic_template: PathBuf, } /// Manage a semantic convention registry and return the exit code. @@ -136,52 +136,65 @@ pub fn semconv_registry(log: impl Logger + Sync + Clone, command: &RegistryComma } }; - match &command.command { - RegistrySubCommand::Check(args) => write_diagnostics( + let (cmd_result, diag_args) = match &command.command { + RegistrySubCommand::Check(args) => ( check::command(log.clone(), &cache, args), args.diagnostic.clone(), - log.clone(), ), - RegistrySubCommand::Generate(args) => write_diagnostics( + RegistrySubCommand::Generate(args) => ( generate::command(log.clone(), &cache, args), args.diagnostic.clone(), - log.clone(), ), - RegistrySubCommand::Stats(args) => { - stats::command(log, &cache, args); - 0 - } - RegistrySubCommand::Resolve(args) => { - resolve::command(log, &cache, args); - 0 - } + RegistrySubCommand::Stats(args) => ( + stats::command(log.clone(), &cache, args), + args.diagnostic.clone(), + ), + RegistrySubCommand::Resolve(args) => ( + resolve::command(log.clone(), &cache, args), + args.diagnostic.clone(), + ), RegistrySubCommand::Search(_) => unimplemented!(), - RegistrySubCommand::UpdateMarkdown(args) => write_diagnostics( + RegistrySubCommand::UpdateMarkdown(args) => ( update_markdown::command(log.clone(), &cache, args), args.diagnostic.clone(), - log.clone(), ), - } + }; + + process_diagnostics(cmd_result, diag_args, log.clone()) } -fn write_diagnostics( - result: Result<(), DiagnosticMessages>, - diagnostic: DiagnosticArgs, +/// Render the diagnostic messages based on the diagnostic configuration and return the exit code +/// based on the diagnostic messages. +fn process_diagnostics( + cmd_result: Result<(), DiagnosticMessages>, + diagnostic_args: DiagnosticArgs, logger: impl Logger + Sync + Clone, ) -> i32 { - if let Err(e) = result { - let config = GeneratorConfig::new(template_root); - let engine = TemplateEngine::try_new(&format!("errors/{}", target), config) - .exit_if_error(logger.clone()); - - engine - .generate(logger.clone(), &e, output.as_path()) - .exit_if_error(logger.clone()); - - 1 - } else { - 0 + if let Err(diag_msgs) = cmd_result { + let config = GeneratorConfig::new(diagnostic_args.diagnostic_template); + match TemplateEngine::try_new(&diagnostic_args.diagnostic_format, config) { + Ok(engine) => { + match engine.generate(logger.clone(), &diag_msgs, PathBuf::new().as_path()) { + Ok(_) => {} + Err(e) => { + logger.error(&format!( + "Failed to render the diagnostic messages. Error: {}", + e + )); + return 1; + } + } + } + Err(e) => { + logger.error(&format!("Failed to create the template engine to render the diagnostic messages. Error: {}", e)); + return 1; + } + } + return if diag_msgs.has_error() { 1 } else { 0 }; } + + // Return 0 if there are no diagnostic messages + 0 } /// Convert a `RegistryPath` to a `weaver_semconv::path::RegistryPath`. @@ -322,10 +335,9 @@ fn check_policies( pub(crate) fn resolve_semconv_specs( registry: &mut SemConvRegistry, logger: impl Logger + Sync + Clone, -) -> ResolvedTelemetrySchema { - let resolved_schema = SchemaResolver::resolve_semantic_convention_registry(registry) - .exit_if_error(logger.clone()); +) -> Result { + let resolved_schema = SchemaResolver::resolve_semantic_convention_registry(registry)?; logger.success("SemConv registry resolved"); - resolved_schema + Ok(resolved_schema) } diff --git a/src/registry/resolve.rs b/src/registry/resolve.rs index 5de8e6b1..d2ea6862 100644 --- a/src/registry/resolve.rs +++ b/src/registry/resolve.rs @@ -8,13 +8,14 @@ use clap::{Args, ValueEnum}; use serde::Serialize; use weaver_cache::Cache; -use weaver_common::error::ExitIfError; +use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; use weaver_semconv::registry::SemConvRegistry; use crate::registry::{ - load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, RegistryArgs, + load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, DiagnosticArgs, + RegistryArgs, }; /// Supported output formats for the resolved schema @@ -54,6 +55,10 @@ pub struct RegistryResolveArgs { /// Example: `--format json` #[arg(short, long, default_value = "yaml")] format: Format, + + /// Parameters to specify the diagnostic format. + #[command(flatten)] + pub diagnostic: DiagnosticArgs, } /// Resolve a semantic convention registry and write the resolved schema to a @@ -63,7 +68,7 @@ pub(crate) fn command( logger: impl Logger + Sync + Clone, cache: &Cache, args: &RegistryResolveArgs, -) { +) -> Result<(), DiagnosticMessages> { logger.loading(&format!("Resolving registry `{}`", args.registry.registry)); let registry_id = "default"; @@ -71,10 +76,9 @@ pub(crate) fn command( semconv_registry_path_from(&args.registry.registry, &args.registry.registry_git_sub_dir); // Load the semantic convention registry into a local cache. - let semconv_specs = - load_semconv_specs(®istry_path, cache, logger.clone()).exit_if_error(logger.clone()); + let semconv_specs = load_semconv_specs(®istry_path, cache, logger.clone())?; let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); - let schema = resolve_semconv_specs(&mut registry, logger.clone()); + let schema = resolve_semconv_specs(&mut registry, logger.clone())?; // Serialize the resolved schema and write it // to a file or print it to stdout. @@ -113,6 +117,8 @@ pub(crate) fn command( // Capture all the errors panic!("{}", e); }); + + Ok(()) } #[cfg(not(tarpaulin_include))] diff --git a/src/registry/stats.rs b/src/registry/stats.rs index e1b6bac4..f02a6c12 100644 --- a/src/registry/stats.rs +++ b/src/registry/stats.rs @@ -3,11 +3,12 @@ //! Compute stats on a semantic convention registry. use crate::registry::{ - load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, RegistryArgs, + load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, DiagnosticArgs, + RegistryArgs, }; use clap::Args; use weaver_cache::Cache; -use weaver_common::error::ExitIfError; +use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::Logger; use weaver_resolved_schema::registry::{CommonGroupStats, GroupStats}; use weaver_resolved_schema::ResolvedTelemetrySchema; @@ -20,11 +21,19 @@ pub struct RegistryStatsArgs { /// Parameters to specify the semantic convention registry #[command(flatten)] registry: RegistryArgs, + + /// Parameters to specify the diagnostic format. + #[command(flatten)] + pub diagnostic: DiagnosticArgs, } /// Compute stats on a semantic convention registry. #[cfg(not(tarpaulin_include))] -pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: &RegistryStatsArgs) { +pub(crate) fn command( + logger: impl Logger + Sync + Clone, + cache: &Cache, + args: &RegistryStatsArgs, +) -> Result<(), DiagnosticMessages> { logger.loading(&format!( "Compute statistics on the registry `{}`", args.registry.registry @@ -35,16 +44,16 @@ pub(crate) fn command(logger: impl Logger + Sync + Clone, cache: &Cache, args: & semconv_registry_path_from(&args.registry.registry, &args.registry.registry_git_sub_dir); // Load the semantic convention registry into a local cache. - let semconv_specs = - load_semconv_specs(®istry_path, cache, logger.clone()).exit_if_error(logger.clone()); + let semconv_specs = load_semconv_specs(®istry_path, cache, logger.clone())?; let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); display_semconv_registry_stats(®istry); // Resolve the semantic convention registry. - let resolved_schema = resolve_semconv_specs(&mut registry, logger); + let resolved_schema = resolve_semconv_specs(&mut registry, logger)?; display_schema_stats(&resolved_schema); + Ok(()) } #[cfg(not(tarpaulin_include))] diff --git a/src/registry/update_markdown.rs b/src/registry/update_markdown.rs index 7b09d00a..611077d0 100644 --- a/src/registry/update_markdown.rs +++ b/src/registry/update_markdown.rs @@ -3,11 +3,10 @@ //! Update markdown files that contain markers indicating the templates used to //! update the specified sections. -use crate::registry::{semconv_registry_path_from, RegistryPath}; +use crate::registry::{semconv_registry_path_from, DiagnosticArgs, RegistryPath}; use clap::Args; use weaver_cache::Cache; use weaver_common::diagnostic::DiagnosticMessages; -use weaver_common::error::ExitIfError; use weaver_common::Logger; use weaver_forge::{GeneratorConfig, TemplateEngine}; use weaver_semconv_gen::{update_markdown, SnippetGenerator}; @@ -52,6 +51,10 @@ pub struct RegistryUpdateMarkdownArgs { /// {templates}/{target}/snippet.md.j2. #[arg(long)] pub target: Option, + + /// Parameters to specify the diagnostic format. + #[command(flatten)] + pub diagnostic: DiagnosticArgs, } /// Update markdown files. @@ -68,15 +71,17 @@ pub(crate) fn command( // Construct a generator if we were given a `--target` argument. let generator = match args.target.as_ref() { None => None, - Some(target) => Some(TemplateEngine::try_new(&format!("registry/{}", target), GeneratorConfig::default())?), + Some(target) => Some(TemplateEngine::try_new( + &format!("registry/{}", target), + GeneratorConfig::default(), + )?), }; let generator = SnippetGenerator::try_from_url( semconv_registry_path_from(&args.registry, &args.registry_git_sub_dir), cache, generator, - ) - .exit_if_error(log.clone()); + )?; log.success("Registry resolved successfully"); let operation = if args.dry_run { "Validating" From eb399fd8a0ef81d508e3ecf72eec1053d233859d Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Thu, 16 May 2024 10:06:40 -0700 Subject: [PATCH 10/17] chore: Merge with main --- Cargo.lock | 2 +- crates/weaver_common/src/diagnostic.rs | 1 - crates/weaver_forge/src/lib.rs | 3 --- diagnostic_templates/ansi/errors.txt.j2 | 14 +++++++------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a6567c4..cefc8e3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3695,8 +3695,8 @@ dependencies = [ "jaq-interpret", "jaq-parse", "jaq-std", - "miette", "markdown", + "miette", "minijinja", "opentelemetry", "opentelemetry-stdout", diff --git a/crates/weaver_common/src/diagnostic.rs b/crates/weaver_common/src/diagnostic.rs index dd794e19..a3b7b6b6 100644 --- a/crates/weaver_common/src/diagnostic.rs +++ b/crates/weaver_common/src/diagnostic.rs @@ -23,7 +23,6 @@ pub struct MietteDiagnosticExt { #[serde(skip_serializing_if = "Option::is_none")] pub code: Option, /// [`Diagnostic`] severity. Intended to be used by - /// [`ReportHandler`](crate::ReportHandler)s to change the way different /// [`Diagnostic`]s are displayed. Defaults to [`Severity::Error`] #[serde(skip_serializing_if = "Option::is_none")] pub severity: Option, diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index 09e33cc0..68c94891 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -28,7 +28,6 @@ use crate::config::{ApplicationMode, CaseConvention, TargetConfig}; use crate::debug::error_summary; use crate::error::Error::InvalidConfigFile; use crate::extensions::acronym::acronym; -use crate::extensions::ansi_code::add_ansi_filters; use crate::extensions::case_converter::case_converter; use crate::extensions::{ansi, code}; use crate::registry::{TemplateGroup, TemplateRegistry}; @@ -506,8 +505,6 @@ impl TemplateEngine { env.add_filter("required", extensions::otel::required); env.add_filter("not_required", extensions::otel::not_required); - add_ansi_filters(&mut env); - // ToDo Implement more filters: stable, experimental, deprecated env.add_test("stable", extensions::otel::is_stable); env.add_test("experimental", extensions::otel::is_experimental); diff --git a/diagnostic_templates/ansi/errors.txt.j2 b/diagnostic_templates/ansi/errors.txt.j2 index c48075ca..7be0f773 100644 --- a/diagnostic_templates/ansi/errors.txt.j2 +++ b/diagnostic_templates/ansi/errors.txt.j2 @@ -1,14 +1,14 @@ {{- template.set_output_type("stdout") }} -{{ "Diagnostic report" | bold | red }}: +{{ "Diagnostic report" | ansi_bold | ansi_red }}: {% for item in ctx %} {%- if item.error.type == "policy_violation" %} -Violation: {{ item.error.violation.id | bold | green }} - - Category : {{ item.error.violation.category | cyan }} - - Type : {{ item.error.violation.type | cyan }} - - SemConv group : {{ item.error.violation.group | cyan }} - - SemConv attribute: {{ item.error.violation.attr | cyan }} - - Provenance: {{ item.error.provenance | cyan }} +Violation: {{ item.error.violation.id | ansi_bold | ansi_green }} + - Category : {{ item.error.violation.category | ansi_cyan }} + - Type : {{ item.error.violation.type | ansi_cyan }} + - SemConv group : {{ item.error.violation.group | ansi_cyan }} + - SemConv attribute: {{ item.error.violation.attr | ansi_cyan }} + - Provenance: {{ item.error.provenance | ansi_cyan }} {% else %} {{ item.diagnostic.ansi_message }} {% endif %} From 790c87961864911e6d281a2e0f414fd99959f71f Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Thu, 16 May 2024 13:43:45 -0700 Subject: [PATCH 11/17] chore(cli): Unify parameters across sub-commands --- src/registry/generate.rs | 8 ++++---- src/registry/resolve.rs | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/registry/generate.rs b/src/registry/generate.rs index 35ad4f5d..169c0228 100644 --- a/src/registry/generate.rs +++ b/src/registry/generate.rs @@ -48,9 +48,9 @@ pub struct RegistryGenerateArgs { pub registry_git_sub_dir: Option, /// Optional list of policy files to check against the files of the semantic - /// convention registry before the resolution process. - #[arg(short = 'b', long)] - pub before_resolution_policies: Vec, + /// convention registry. + #[arg(short = 'p', long)] + pub policies: Vec, /// Parameters to specify the diagnostic format. #[command(flatten)] @@ -78,7 +78,7 @@ pub(crate) fn command( check_policies( ®istry_path, cache, - &args.before_resolution_policies, + &args.policies, &semconv_specs, logger.clone(), )?; diff --git a/src/registry/resolve.rs b/src/registry/resolve.rs index d2ea6862..e0de93d4 100644 --- a/src/registry/resolve.rs +++ b/src/registry/resolve.rs @@ -13,10 +13,7 @@ use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; use weaver_semconv::registry::SemConvRegistry; -use crate::registry::{ - load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, DiagnosticArgs, - RegistryArgs, -}; +use crate::registry::{load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, DiagnosticArgs, RegistryArgs, check_policies}; /// Supported output formats for the resolved schema #[derive(Debug, Clone, ValueEnum)] @@ -56,6 +53,11 @@ pub struct RegistryResolveArgs { #[arg(short, long, default_value = "yaml")] format: Format, + /// Optional list of policy files to check against the files of the semantic + /// convention registry. + #[arg(short = 'p', long)] + pub policies: Vec, + /// Parameters to specify the diagnostic format. #[command(flatten)] pub diagnostic: DiagnosticArgs, @@ -77,6 +79,15 @@ pub(crate) fn command( // Load the semantic convention registry into a local cache. let semconv_specs = load_semconv_specs(®istry_path, cache, logger.clone())?; + + check_policies( + ®istry_path, + cache, + &args.policies, + &semconv_specs, + logger.clone(), + )?; + let mut registry = SemConvRegistry::from_semconv_specs(registry_id, semconv_specs); let schema = resolve_semconv_specs(&mut registry, logger.clone())?; From 0f66337844d4a532bfb4e3e58ace0f6d81b16389 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Fri, 17 May 2024 08:26:05 -0700 Subject: [PATCH 12/17] chore(cli): Add test infrastructure for the command line --- Cargo.lock | 70 +++++++++++++++++ Cargo.toml | 3 + crates/weaver_checker/src/lib.rs | 20 ++++- crates/weaver_forge/src/lib.rs | 77 ++++++++----------- crates/weaver_resolver/src/lib.rs | 4 + diagnostic_templates/ansi/errors.txt.j2 | 2 - .../gh_workflow_command/errors.txt.j2 | 1 - diagnostic_templates/json/errors.json.j2 | 1 - src/main.rs | 6 +- src/registry/generate.rs | 4 +- src/registry/mod.rs | 4 +- tests/registry_check.rs | 45 +++++++++++ 12 files changed, 176 insertions(+), 61 deletions(-) create mode 100644 tests/registry_check.rs diff --git a/Cargo.lock b/Cargo.lock index cefc8e3e..f24150ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,21 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "assert_cmd" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-trait" version = "0.1.80" @@ -520,6 +535,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -552,6 +573,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dunce" version = "1.0.4" @@ -2492,6 +2519,33 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.82" @@ -3154,6 +3208,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "textwrap" version = "0.16.1" @@ -3501,6 +3561,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -3610,6 +3679,7 @@ dependencies = [ name = "weaver" version = "0.1.0" dependencies = [ + "assert_cmd", "clap", "rayon", "serde", diff --git a/Cargo.toml b/Cargo.toml index e53ee6dd..6474bc92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,9 @@ walkdir.workspace = true rayon = "1.10.0" +[dev-dependencies] +assert_cmd = "2.0.14" + [profile.release] lto = true strip = true diff --git a/crates/weaver_checker/src/lib.rs b/crates/weaver_checker/src/lib.rs index 26361527..20a3abb2 100644 --- a/crates/weaver_checker/src/lib.rs +++ b/crates/weaver_checker/src/lib.rs @@ -26,7 +26,11 @@ pub mod violation; pub enum Error { /// An invalid policy. #[error("Invalid policy file '{file}', error: {error})")] - #[diagnostic()] + #[diagnostic( + severity = "error", + url("https://www.openpolicyagent.org/docs/latest/policy-language/"), + help("Check the policy file for syntax errors."), + )] InvalidPolicyFile { /// The file that caused the error. file: String, @@ -36,7 +40,11 @@ pub enum Error { /// An invalid policy glob pattern. #[error("Invalid policy glob pattern '{pattern}', error: {error})")] - #[diagnostic()] + #[diagnostic( + severity = "error", + url("https://docs.rs/globset/latest/globset/"), + help("Check the glob pattern for syntax errors."), + )] InvalidPolicyGlobPattern { /// The glob pattern that caused the error. pattern: String, @@ -62,7 +70,9 @@ pub enum Error { /// Violation evaluation error. #[error("Violation evaluation error: {error}")] - #[diagnostic()] + #[diagnostic( + severity = "error", + )] ViolationEvaluationError { /// The error that occurred. error: String, @@ -70,7 +80,9 @@ pub enum Error { /// A policy violation error. #[error("Policy violation: {violation}, provenance: {provenance}")] - #[diagnostic()] + #[diagnostic( + severity = "error", + )] PolicyViolation { /// The provenance of the violation (URL or path). provenance: String, diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index 68c94891..5071c5f9 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -2,15 +2,15 @@ #![doc = include_str!("../README.md")] +use std::{fs, io}; use std::borrow::Cow; use std::fmt::{Debug, Display, Formatter}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; -use std::{fs, io}; +use minijinja::{Environment, ErrorKind, State, Value}; use minijinja::syntax::SyntaxConfig; use minijinja::value::{from_args, Object}; -use minijinja::{Environment, ErrorKind, State, Value}; use rayon::iter::IntoParallelIterator; use rayon::iter::ParallelIterator; use serde::Serialize; @@ -27,9 +27,9 @@ use weaver_common::Logger; use crate::config::{ApplicationMode, CaseConvention, TargetConfig}; use crate::debug::error_summary; use crate::error::Error::InvalidConfigFile; +use crate::extensions::{ansi, code}; use crate::extensions::acronym::acronym; use crate::extensions::case_converter::case_converter; -use crate::extensions::{ansi, code}; use crate::registry::{TemplateGroup, TemplateRegistry}; mod config; @@ -65,9 +65,9 @@ impl GeneratorConfig { } } -/// The type of output where the generated content will be written. +/// Enumeration defining where the output of program execution should be directed. #[derive(Debug, Clone)] -pub enum OutputType { +pub enum OutputDirective { /// Write the generated content to the standard output. Stdout, /// Write the generated content to the standard error. @@ -80,7 +80,6 @@ pub enum OutputType { #[derive(Debug, Clone)] struct TemplateObject { file_name: Arc>, - output_type: Arc>, } impl TemplateObject { @@ -107,24 +106,9 @@ impl Object for TemplateObject { args: &[Value], ) -> Result { if name == "set_file_name" { - let (file_name,): (&str,) = from_args(args)?; + let (file_name, ): (&str, ) = from_args(args)?; file_name.clone_into(&mut self.file_name.lock().expect("Lock poisoned")); Ok(Value::from("")) - } else if name == "set_output_type" { - let (output_type,): (&str,) = from_args(args)?; - let output_type = match output_type { - "stdout" => OutputType::Stdout, - "stderr" => OutputType::Stderr, - "file" => OutputType::File, - _ => { - return Err(minijinja::Error::new( - ErrorKind::UnknownMethod, - format!("unknown output type: {output_type}"), - )); - } - }; - output_type.clone_into(&mut self.output_type.lock().expect("Lock poisoned")); - Ok(Value::from("")) } else { Err(minijinja::Error::new( ErrorKind::UnknownMethod, @@ -239,6 +223,7 @@ impl TemplateEngine { log: impl Logger + Clone + Sync, context: &T, output_dir: &Path, + output_directive: &OutputDirective, ) -> Result<(), Error> { // List all files in the target directory and its subdirectories let files: Vec = WalkDir::new(self.path.clone()) @@ -295,9 +280,10 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, + output_directive, output_dir, ) { return Some(e); @@ -315,6 +301,7 @@ impl TemplateEngine { log.clone(), NewContext { ctx: result }.try_into().ok()?, relative_path, + output_directive, output_dir, ) { return Some(e); @@ -330,9 +317,10 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, + output_directive, output_dir, ) { return Some(e); @@ -354,13 +342,11 @@ impl TemplateEngine { log: impl Logger + Clone + Sync, ctx: serde_json::Value, template_path: &Path, + output_directive: &OutputDirective, output_dir: &Path, ) -> Result<(), Error> { let template_object = TemplateObject { - file_name: Arc::new(Mutex::new( - template_path.to_str().unwrap_or_default().to_owned(), - )), - output_type: Arc::new(Mutex::new(OutputType::File)), + file_name: Arc::new(Mutex::new("".to_owned())), }; let mut engine = self.template_engine()?; let template_file = template_path.to_str().ok_or(InvalidTemplateFile { @@ -391,19 +377,14 @@ impl TemplateEngine { error_id: e.to_string(), error: error_summary(e), })?; - match template_object - .output_type - .lock() - .expect("Lock poisoned") - .clone() - { - OutputType::Stdout => { + match output_directive { + OutputDirective::Stdout => { println!("{}", output); } - OutputType::Stderr => { + OutputDirective::Stderr => { eprintln!("{}", output); } - OutputType::File => { + OutputDirective::File => { let generated_file = Self::save_generated_code(output_dir, template_object.file_name(), output)?; log.success(&format!("Generated file {:?}", generated_file)); @@ -557,7 +538,7 @@ fn cross_platform_loader<'x, P: AsRef + 'x>( ErrorKind::InvalidOperation, "could not read template", ) - .with_source(err)), + .with_source(err)), } } } @@ -629,7 +610,7 @@ fn split_id(value: Value) -> Result, minijinja::Error> { mod tests { use std::collections::HashSet; use std::fs; - use std::path::Path; + use std::path::{Path, PathBuf}; use globset::Glob; use walkdir::WalkDir; @@ -642,6 +623,7 @@ mod tests { use crate::config::{ApplicationMode, TemplateConfig}; use crate::debug::print_dedup_errors; use crate::filter::Filter; + use crate::OutputDirective; use crate::registry::TemplateRegistry; #[test] @@ -791,18 +773,19 @@ mod tests { schema.registry(registry_id).expect("registry not found"), schema.catalog(), ) - .unwrap_or_else(|e| { - panic!( - "Failed to create the context for the template evaluation: {:?}", - e - ) - }); + .unwrap_or_else(|e| { + panic!( + "Failed to create the context for the template evaluation: {:?}", + e + ) + }); engine .generate( logger.clone(), &template_registry, Path::new("observed_output"), + &OutputDirective::File(PathBuf::from("observed_output")), ) .inspect_err(|e| { print_dedup_errors(logger.clone(), e.clone()); diff --git a/crates/weaver_resolver/src/lib.rs b/crates/weaver_resolver/src/lib.rs index ff2f031a..4429e3d2 100644 --- a/crates/weaver_resolver/src/lib.rs +++ b/crates/weaver_resolver/src/lib.rs @@ -39,6 +39,10 @@ pub struct SchemaResolver {} pub enum Error { /// An invalid URL. #[error("Invalid URL `{url:?}`, error: {error:?})")] + #[diagnostic( + severity = "error", + help("Check the URL and try again.") + )] InvalidUrl { /// The invalid URL. url: String, diff --git a/diagnostic_templates/ansi/errors.txt.j2 b/diagnostic_templates/ansi/errors.txt.j2 index 7be0f773..b1c5e847 100644 --- a/diagnostic_templates/ansi/errors.txt.j2 +++ b/diagnostic_templates/ansi/errors.txt.j2 @@ -1,5 +1,3 @@ -{{- template.set_output_type("stdout") }} - {{ "Diagnostic report" | ansi_bold | ansi_red }}: {% for item in ctx %} {%- if item.error.type == "policy_violation" %} diff --git a/diagnostic_templates/gh_workflow_command/errors.txt.j2 b/diagnostic_templates/gh_workflow_command/errors.txt.j2 index ad96a03d..9d250d02 100644 --- a/diagnostic_templates/gh_workflow_command/errors.txt.j2 +++ b/diagnostic_templates/gh_workflow_command/errors.txt.j2 @@ -1,4 +1,3 @@ -{{- template.set_output_type("stdout") }} {%- set policy_violations = ctx | selectattr("error.type", "equalto", "policy_violation") | list %} {%- set other_diagnostics = ctx | rejectattr("error.type", "equalto", "policy_violation") | list %} diff --git a/diagnostic_templates/json/errors.json.j2 b/diagnostic_templates/json/errors.json.j2 index 9a4b6bfd..86ce8997 100644 --- a/diagnostic_templates/json/errors.json.j2 +++ b/diagnostic_templates/json/errors.json.j2 @@ -1,2 +1 @@ -{{- template.set_output_type("stdout") -}} {{ ctx | tojson(true) }} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3b1b7fd7..e6b532cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,10 @@ fn main() { run_command(&cli, log) }; - let elapsed = start.elapsed(); - println!("Total execution time: {:?}s", elapsed.as_secs_f64()); + if !cli.quiet { + let elapsed = start.elapsed(); + println!("Total execution time: {:?}s", elapsed.as_secs_f64()); + } // Exit the process with the exit code provided by the `run_command` function. #[allow(clippy::exit)] diff --git a/src/registry/generate.rs b/src/registry/generate.rs index 169c0228..7c2f8447 100644 --- a/src/registry/generate.rs +++ b/src/registry/generate.rs @@ -10,7 +10,7 @@ use weaver_cache::Cache; use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; -use weaver_forge::{GeneratorConfig, TemplateEngine}; +use weaver_forge::{GeneratorConfig, OutputDirective, TemplateEngine}; use weaver_semconv::registry::SemConvRegistry; use crate::registry::{ @@ -95,7 +95,7 @@ pub(crate) fn command( schema.catalog(), )?; - engine.generate(logger.clone(), &template_registry, args.output.as_path())?; + engine.generate(logger.clone(), &template_registry, args.output.as_path(), &OutputDirective::File)?; logger.success("Artifacts generated successfully"); Ok(()) diff --git a/src/registry/mod.rs b/src/registry/mod.rs index dd9724dd..644554ce 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -16,7 +16,7 @@ use weaver_checker::{Engine, Error, PolicyPackage}; use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::error::handle_errors; use weaver_common::Logger; -use weaver_forge::{GeneratorConfig, TemplateEngine}; +use weaver_forge::{GeneratorConfig, OutputDirective, TemplateEngine}; use weaver_resolved_schema::ResolvedTelemetrySchema; use weaver_resolver::SchemaResolver; use weaver_semconv::registry::SemConvRegistry; @@ -174,7 +174,7 @@ fn process_diagnostics( let config = GeneratorConfig::new(diagnostic_args.diagnostic_template); match TemplateEngine::try_new(&diagnostic_args.diagnostic_format, config) { Ok(engine) => { - match engine.generate(logger.clone(), &diag_msgs, PathBuf::new().as_path()) { + match engine.generate(logger.clone(), &diag_msgs, PathBuf::new().as_path(), &OutputDirective::Stdout) { Ok(_) => {} Err(e) => { logger.error(&format!( diff --git a/tests/registry_check.rs b/tests/registry_check.rs new file mode 100644 index 00000000..04703ab3 --- /dev/null +++ b/tests/registry_check.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Test the registry check command. + +use assert_cmd::Command; + +#[test] +fn test_registry_check() { + // Test OTel official semantic convention registry. + // This test requires internet access to fetch the registry. + // This registry should always be valid! + let mut cmd = Command::cargo_bin("weaver").unwrap(); + let output = cmd + .arg("registry") + .arg("check") + .timeout(std::time::Duration::from_secs(60)) + .output() + .expect("failed to execute process"); + + assert!(output.status.success()); + + // Test a local semantic convention registry. + // There are policy violations in this registry. + // This test should fail with a non-zero exit code and display the policy violations. + let mut cmd = Command::cargo_bin("weaver").unwrap(); + let output = cmd + .arg("--quiet") + .arg("registry") + .arg("check") + .arg("-r") + .arg("crates/weaver_codegen_test/semconv_registry/") + .arg("--diagnostic-format") + .arg("json") + .timeout(std::time::Duration::from_secs(60)) + .output() + .expect("failed to execute process"); + + assert!(!output.status.success()); + + // We should be able to parse the JSON output from stdout. + let stdout = String::from_utf8(output.stdout).expect("Invalid UTF-8"); + let json_value: Vec = serde_json::from_str(&stdout).expect("Invalid JSON"); + // We expect 13 policy violations. + assert_eq!(json_value.len(), 13); +} \ No newline at end of file From 5b5b525a2bfccf0cea88de05a0d34947b4667fe6 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Fri, 17 May 2024 15:25:43 -0700 Subject: [PATCH 13/17] feat(CLI): Support --diagnostic-format and --diagnostic-templates on all the commands. --- Cargo.lock | 48 ++++++----- Dockerfile | 1 + crates/weaver_checker/src/lib.rs | 12 +-- crates/weaver_codegen_test/build.rs | 9 ++- crates/weaver_forge/src/lib.rs | 46 ++++++----- crates/weaver_resolver/src/lib.rs | 5 +- docs/usage.md | 119 +++++++++++++++++++++++----- src/registry/check.rs | 2 +- src/registry/generate.rs | 9 ++- src/registry/mod.rs | 39 ++++++++- src/registry/resolve.rs | 7 +- tests/registry_check.rs | 2 +- tests/registry_generate.rs | 47 +++++++++++ 13 files changed, 259 insertions(+), 87 deletions(-) create mode 100644 tests/registry_generate.rs diff --git a/Cargo.lock b/Cargo.lock index f24150ae..5e150198 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "27a4bd113ab6da4cd0f521068a6e2ee1065eab54107266a11835d02c8ec86a37" [[package]] name = "arc-swap" @@ -154,6 +154,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -593,9 +599,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "encoding_rs" @@ -782,9 +788,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -1582,15 +1588,15 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap", "slab", @@ -1999,9 +2005,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -2021,9 +2027,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -2151,9 +2157,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -3140,9 +3146,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.63" +version = "2.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f" dependencies = [ "proc-macro2", "quote", @@ -3227,18 +3233,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/Dockerfile b/Dockerfile index 267ad539..58387c05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,7 @@ COPY crates /build/crates COPY data /build/data COPY src /build/src COPY tests build/tests +COPY diagnostic_templates /build/diagnostic_templates # Don't build release, so we get template debugging output. RUN cargo build diff --git a/crates/weaver_checker/src/lib.rs b/crates/weaver_checker/src/lib.rs index 20a3abb2..abd30d68 100644 --- a/crates/weaver_checker/src/lib.rs +++ b/crates/weaver_checker/src/lib.rs @@ -29,7 +29,7 @@ pub enum Error { #[diagnostic( severity = "error", url("https://www.openpolicyagent.org/docs/latest/policy-language/"), - help("Check the policy file for syntax errors."), + help("Check the policy file for syntax errors.") )] InvalidPolicyFile { /// The file that caused the error. @@ -43,7 +43,7 @@ pub enum Error { #[diagnostic( severity = "error", url("https://docs.rs/globset/latest/globset/"), - help("Check the glob pattern for syntax errors."), + help("Check the glob pattern for syntax errors.") )] InvalidPolicyGlobPattern { /// The glob pattern that caused the error. @@ -70,9 +70,7 @@ pub enum Error { /// Violation evaluation error. #[error("Violation evaluation error: {error}")] - #[diagnostic( - severity = "error", - )] + #[diagnostic(severity = "error")] ViolationEvaluationError { /// The error that occurred. error: String, @@ -80,9 +78,7 @@ pub enum Error { /// A policy violation error. #[error("Policy violation: {violation}, provenance: {provenance}")] - #[diagnostic( - severity = "error", - )] + #[diagnostic(severity = "error")] PolicyViolation { /// The provenance of the violation (URL or path). provenance: String, diff --git a/crates/weaver_codegen_test/build.rs b/crates/weaver_codegen_test/build.rs index f4873166..45564ff3 100644 --- a/crates/weaver_codegen_test/build.rs +++ b/crates/weaver_codegen_test/build.rs @@ -14,7 +14,7 @@ use weaver_cache::Cache; use weaver_common::in_memory::LogMessage; use weaver_common::{in_memory, Logger}; use weaver_forge::registry::TemplateRegistry; -use weaver_forge::{GeneratorConfig, TemplateEngine}; +use weaver_forge::{GeneratorConfig, OutputDirective, TemplateEngine}; use weaver_resolver::SchemaResolver; use weaver_semconv::path::RegistryPath; use weaver_semconv::registry::SemConvRegistry; @@ -59,7 +59,12 @@ fn main() { .unwrap_or_else(|e| process_error(&logger, e)); let target_dir: PathBuf = target_dir.into(); engine - .generate(logger.clone(), &template_registry, target_dir.as_path()) + .generate( + logger.clone(), + &template_registry, + target_dir.as_path(), + &OutputDirective::File, + ) .unwrap_or_else(|e| process_error(&logger, e)); print_logs(&logger); diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index 5071c5f9..e908264d 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -2,15 +2,15 @@ #![doc = include_str!("../README.md")] -use std::{fs, io}; use std::borrow::Cow; use std::fmt::{Debug, Display, Formatter}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; +use std::{fs, io}; -use minijinja::{Environment, ErrorKind, State, Value}; use minijinja::syntax::SyntaxConfig; use minijinja::value::{from_args, Object}; +use minijinja::{Environment, ErrorKind, State, Value}; use rayon::iter::IntoParallelIterator; use rayon::iter::ParallelIterator; use serde::Serialize; @@ -27,9 +27,9 @@ use weaver_common::Logger; use crate::config::{ApplicationMode, CaseConvention, TargetConfig}; use crate::debug::error_summary; use crate::error::Error::InvalidConfigFile; -use crate::extensions::{ansi, code}; use crate::extensions::acronym::acronym; use crate::extensions::case_converter::case_converter; +use crate::extensions::{ansi, code}; use crate::registry::{TemplateGroup, TemplateRegistry}; mod config; @@ -106,7 +106,7 @@ impl Object for TemplateObject { args: &[Value], ) -> Result { if name == "set_file_name" { - let (file_name, ): (&str, ) = from_args(args)?; + let (file_name,): (&str,) = from_args(args)?; file_name.clone_into(&mut self.file_name.lock().expect("Lock poisoned")); Ok(Value::from("")) } else { @@ -280,8 +280,8 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, output_directive, output_dir, @@ -317,8 +317,8 @@ impl TemplateEngine { NewContext { ctx: &filtered_result, } - .try_into() - .ok()?, + .try_into() + .ok()?, relative_path, output_directive, output_dir, @@ -345,8 +345,14 @@ impl TemplateEngine { output_directive: &OutputDirective, output_dir: &Path, ) -> Result<(), Error> { + let file_name = template_path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .trim_end_matches(".j2") + .to_owned(); let template_object = TemplateObject { - file_name: Arc::new(Mutex::new("".to_owned())), + file_name: Arc::new(Mutex::new(file_name)), }; let mut engine = self.template_engine()?; let template_file = template_path.to_str().ok_or(InvalidTemplateFile { @@ -356,7 +362,7 @@ impl TemplateEngine { engine.add_global("template", Value::from_object(template_object.clone())); - log.loading(&format!("Generating file {}", template_file)); + log.loading(&format!("Rendering template {}", template_file)); let template = engine.get_template(template_file).map_err(|e| { let templates = engine @@ -538,7 +544,7 @@ fn cross_platform_loader<'x, P: AsRef + 'x>( ErrorKind::InvalidOperation, "could not read template", ) - .with_source(err)), + .with_source(err)), } } } @@ -610,7 +616,7 @@ fn split_id(value: Value) -> Result, minijinja::Error> { mod tests { use std::collections::HashSet; use std::fs; - use std::path::{Path, PathBuf}; + use std::path::Path; use globset::Glob; use walkdir::WalkDir; @@ -623,8 +629,8 @@ mod tests { use crate::config::{ApplicationMode, TemplateConfig}; use crate::debug::print_dedup_errors; use crate::filter::Filter; - use crate::OutputDirective; use crate::registry::TemplateRegistry; + use crate::OutputDirective; #[test] fn test_case_converter() { @@ -773,19 +779,19 @@ mod tests { schema.registry(registry_id).expect("registry not found"), schema.catalog(), ) - .unwrap_or_else(|e| { - panic!( - "Failed to create the context for the template evaluation: {:?}", - e - ) - }); + .unwrap_or_else(|e| { + panic!( + "Failed to create the context for the template evaluation: {:?}", + e + ) + }); engine .generate( logger.clone(), &template_registry, Path::new("observed_output"), - &OutputDirective::File(PathBuf::from("observed_output")), + &OutputDirective::File, ) .inspect_err(|e| { print_dedup_errors(logger.clone(), e.clone()); diff --git a/crates/weaver_resolver/src/lib.rs b/crates/weaver_resolver/src/lib.rs index 4429e3d2..9853b04c 100644 --- a/crates/weaver_resolver/src/lib.rs +++ b/crates/weaver_resolver/src/lib.rs @@ -39,10 +39,7 @@ pub struct SchemaResolver {} pub enum Error { /// An invalid URL. #[error("Invalid URL `{url:?}`, error: {error:?})")] - #[diagnostic( - severity = "error", - help("Check the URL and try again.") - )] + #[diagnostic(severity = "error", help("Check the URL and try again."))] InvalidUrl { /// The invalid URL. url: String, diff --git a/docs/usage.md b/docs/usage.md index 53b4888f..46c6b605 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -19,51 +19,113 @@ Options: ## registry check ``` -Validates a registry (i.e., parsing, resolution of references, extends clauses, and constraints) +Validates a semantic convention registry. + +The validation process for a semantic convention registry involves several steps: +- Loading the semantic convention specifications from a local directory or a git repository. +- Parsing the loaded semantic convention specifications. +- Resolving references, extends clauses, and constraints within the specifications. +- Checking compliance with specified Rego policies, if provided. + +Note: The `-d` and `--registry-git-sub-dir` options are only used when the registry is a Git URL otherwise these options are ignored. + +The process exits with a code of 0 if the registry validation is successful. Usage: weaver registry check [OPTIONS] Options: -r, --registry - Local path or Git URL of the semantic convention registry to check [default: https://github.com/open-telemetry/semantic-conventions.git] + Local path or Git URL of the semantic convention registry to check + + [default: https://github.com/open-telemetry/semantic-conventions.git] + -d, --registry-git-sub-dir - Optional path in the Git repository where the semantic convention registry is located [default: model] - -b, --before-resolution-policies - Optional list of policy files to check against the files of the semantic convention registry before the resolution process + Optional path in the Git repository where the semantic convention registry is located + + [default: model] + + -p, --policy + Optional list of policy files to check against the files of the semantic convention registry + + --diagnostic-format + Format used to render the diagnostic messages. Predefined formats are: ansi, json, gh_workflow_command + + [default: ansi] + + --diagnostic-template + Path to the directory where the diagnostic templates are located + + [default: diagnostic_templates] + -h, --help - Print help + Print help (see a summary with '-h') ``` -> Note: The `-d` and `--registry-git-sub-dir` options are only used when the -> registry is a Git URL otherwise these options are ignored. - ## registry generate ``` -Generates artifacts from a registry +Generates artifacts from a semantic convention registry. + +Rego policies present in the registry or specified using -p or --policy will be automatically validated by the policy engine before the artifact generation phase. + +Note: The `-d` and `--registry-git-sub-dir` options are only used when the registry is a Git URL otherwise these options are ignored. + +The process exits with a code of 0 if the generation is successful. Usage: weaver registry generate [OPTIONS] [OUTPUT] Arguments: - Target to generate the artifacts for - [OUTPUT] Path to the directory where the generated artifacts will be saved. Default is the `output` directory [default: output] + + Target to generate the artifacts for + + [OUTPUT] + Path to the directory where the generated artifacts will be saved. Default is the `output` directory + + [default: output] Options: -t, --templates - Path to the directory where the templates are located. Default is the `templates` directory [default: templates] + Path to the directory where the templates are located. Default is the `templates` directory + + [default: templates] + -r, --registry - Local path or Git URL of the semantic convention registry [default: https://github.com/open-telemetry/semantic-conventions.git] + Local path or Git URL of the semantic convention registry + + [default: https://github.com/open-telemetry/semantic-conventions.git] + -d, --registry-git-sub-dir - Optional path in the Git repository where the semantic convention registry is located [default: model] -``` + Optional path in the Git repository where the semantic convention registry is located -> Note: The `-d` and `--registry-git-sub-dir` options are only used when the -> registry is a Git URL otherwise these options are ignored. + [default: model] + + -p, --policy + Optional list of policy files to check against the files of the semantic convention registry + + --diagnostic-format + Format used to render the diagnostic messages. Predefined formats are: ansi, json, gh_workflow_command + + [default: ansi] + + --diagnostic-template + Path to the directory where the diagnostic templates are located + + [default: diagnostic_templates] + + -h, --help + Print help (see a summary with '-h') +``` ## registry resolve ``` -Resolves a registry +Resolves a semantic convention registry. + +Rego policies present in the registry or specified using -p or --policy will be automatically validated by the policy engine before the artifact generation phase. + +Note: The `-d` and `--registry-git-sub-dir` options are only used when the registry is a Git URL otherwise these options are ignored. + +The process exits with a code of 0 if the resolution is successful. Usage: weaver registry resolve [OPTIONS] @@ -95,10 +157,23 @@ Options: Possible values: - yaml: YAML format - json: JSON format -``` -> Note: The `-d` and `--registry-git-sub-dir` options are only used when the -> registry is a Git URL otherwise these options are ignored. + -p, --policy + Optional list of policy files to check against the files of the semantic convention registry + + --diagnostic-format + Format used to render the diagnostic messages. Predefined formats are: ansi, json, gh_workflow_command + + [default: ansi] + + --diagnostic-template + Path to the directory where the diagnostic templates are located + + [default: diagnostic_templates] + + -h, --help + Print help (see a summary with '-h') +``` ## registry update-markdown diff --git a/src/registry/check.rs b/src/registry/check.rs index f2b2055c..a61d7de4 100644 --- a/src/registry/check.rs +++ b/src/registry/check.rs @@ -31,7 +31,7 @@ pub struct RegistryCheckArgs { /// Optional list of policy files to check against the files of the semantic /// convention registry. - #[arg(short = 'p', long)] + #[arg(short = 'p', long = "policy")] pub policies: Vec, /// Parameters to specify the diagnostic format. diff --git a/src/registry/generate.rs b/src/registry/generate.rs index 7c2f8447..ff01dc66 100644 --- a/src/registry/generate.rs +++ b/src/registry/generate.rs @@ -49,7 +49,7 @@ pub struct RegistryGenerateArgs { /// Optional list of policy files to check against the files of the semantic /// convention registry. - #[arg(short = 'p', long)] + #[arg(short = 'p', long = "policy")] pub policies: Vec, /// Parameters to specify the diagnostic format. @@ -95,7 +95,12 @@ pub(crate) fn command( schema.catalog(), )?; - engine.generate(logger.clone(), &template_registry, args.output.as_path(), &OutputDirective::File)?; + engine.generate( + logger.clone(), + &template_registry, + args.output.as_path(), + &OutputDirective::File, + )?; logger.success("Artifacts generated successfully"); Ok(()) diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 644554ce..8bfe15c3 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -45,12 +45,38 @@ pub struct RegistryCommand { /// Sub-commands to manage a `registry`. #[derive(Debug, Subcommand)] +#[clap(verbatim_doc_comment)] pub enum RegistrySubCommand { - /// Validates a registry (i.e., parsing, resolution of references, extends clauses, and constraints). + /// Validates a semantic convention registry. + /// + /// The validation process for a semantic convention registry involves several steps: + /// - Loading the semantic convention specifications from a local directory or a git repository. + /// - Parsing the loaded semantic convention specifications. + /// - Resolving references, extends clauses, and constraints within the specifications. + /// - Checking compliance with specified Rego policies, if provided. + /// + /// Note: The `-d` and `--registry-git-sub-dir` options are only used when the registry is a Git URL otherwise these options are ignored. + /// + /// The process exits with a code of 0 if the registry validation is successful. + #[clap(verbatim_doc_comment)] Check(RegistryCheckArgs), - /// Generates artifacts from a registry. + /// Generates artifacts from a semantic convention registry. + /// + /// Rego policies present in the registry or specified using -p or --policy will be automatically validated by the policy engine before the artifact generation phase. + /// + /// Note: The `-d` and `--registry-git-sub-dir` options are only used when the registry is a Git URL otherwise these options are ignored. + /// + /// The process exits with a code of 0 if the generation is successful. + #[clap(verbatim_doc_comment)] Generate(RegistryGenerateArgs), - /// Resolves a registry. + /// Resolves a semantic convention registry. + /// + /// Rego policies present in the registry or specified using -p or --policy will be automatically validated by the policy engine before the artifact generation phase. + /// + /// Note: The `-d` and `--registry-git-sub-dir` options are only used when the registry is a Git URL otherwise these options are ignored. + /// + /// The process exits with a code of 0 if the resolution is successful. + #[clap(verbatim_doc_comment)] Resolve(RegistryResolveArgs), /// Searches a registry (not yet implemented). Search(RegistrySearchArgs), @@ -174,7 +200,12 @@ fn process_diagnostics( let config = GeneratorConfig::new(diagnostic_args.diagnostic_template); match TemplateEngine::try_new(&diagnostic_args.diagnostic_format, config) { Ok(engine) => { - match engine.generate(logger.clone(), &diag_msgs, PathBuf::new().as_path(), &OutputDirective::Stdout) { + match engine.generate( + logger.clone(), + &diag_msgs, + PathBuf::new().as_path(), + &OutputDirective::Stdout, + ) { Ok(_) => {} Err(e) => { logger.error(&format!( diff --git a/src/registry/resolve.rs b/src/registry/resolve.rs index e0de93d4..4abb5c8f 100644 --- a/src/registry/resolve.rs +++ b/src/registry/resolve.rs @@ -13,7 +13,10 @@ use weaver_common::Logger; use weaver_forge::registry::TemplateRegistry; use weaver_semconv::registry::SemConvRegistry; -use crate::registry::{load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, DiagnosticArgs, RegistryArgs, check_policies}; +use crate::registry::{ + check_policies, load_semconv_specs, resolve_semconv_specs, semconv_registry_path_from, + DiagnosticArgs, RegistryArgs, +}; /// Supported output formats for the resolved schema #[derive(Debug, Clone, ValueEnum)] @@ -55,7 +58,7 @@ pub struct RegistryResolveArgs { /// Optional list of policy files to check against the files of the semantic /// convention registry. - #[arg(short = 'p', long)] + #[arg(short = 'p', long = "policy")] pub policies: Vec, /// Parameters to specify the diagnostic format. diff --git a/tests/registry_check.rs b/tests/registry_check.rs index 04703ab3..f06032c0 100644 --- a/tests/registry_check.rs +++ b/tests/registry_check.rs @@ -42,4 +42,4 @@ fn test_registry_check() { let json_value: Vec = serde_json::from_str(&stdout).expect("Invalid JSON"); // We expect 13 policy violations. assert_eq!(json_value.len(), 13); -} \ No newline at end of file +} diff --git a/tests/registry_generate.rs b/tests/registry_generate.rs new file mode 100644 index 00000000..1402addb --- /dev/null +++ b/tests/registry_generate.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Test the registry generate command. + +use assert_cmd::Command; + +#[test] +fn test_registry_generate() { + let mut cmd = Command::cargo_bin("weaver").unwrap(); + let output = cmd + .arg("registry") + .arg("generate") + .timeout(std::time::Duration::from_secs(60)) + .output() + .expect("failed to execute process"); + + // The target and the output directory are not provided, so the command should fail. + assert!(!output.status.success()); + + // Test a local semantic convention registry. + // There are policy violations in this registry. + // This test should fail with a non-zero exit code and display the policy violations. + let mut cmd = Command::cargo_bin("weaver").unwrap(); + let output = cmd + .arg("--quiet") + .arg("registry") + .arg("generate") + .arg("-r") + .arg("crates/weaver_codegen_test/semconv_registry/") + .arg("-t") + .arg("crates/weaver_codegen_test/templates/") + .arg("--diagnostic-format") + .arg("json") + .arg("rust") + .arg("output/") + .timeout(std::time::Duration::from_secs(60)) + .output() + .expect("failed to execute process"); + + assert!(!output.status.success()); + + // We should be able to parse the JSON output from stdout. + let stdout = String::from_utf8(output.stdout).expect("Invalid UTF-8"); + let json_value: Vec = serde_json::from_str(&stdout).expect("Invalid JSON"); + // We expect 13 policy violations. + assert_eq!(json_value.len(), 13); +} From e3b609e5f922c5434c88cd2f9491995acfb9fd6d Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Fri, 17 May 2024 16:11:02 -0700 Subject: [PATCH 14/17] chore: Clean-up before to submit the PR --- crates/weaver_checker/src/lib.rs | 26 +++++++++++++------------- crates/weaver_forge/src/lib.rs | 5 +++-- deny.toml | 7 +------ src/registry/mod.rs | 4 ++-- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/crates/weaver_checker/src/lib.rs b/crates/weaver_checker/src/lib.rs index abd30d68..821d4934 100644 --- a/crates/weaver_checker/src/lib.rs +++ b/crates/weaver_checker/src/lib.rs @@ -113,22 +113,22 @@ impl WeaverError for Error { } } -/// A list of supported policy packages. -pub enum PolicyPackage { +/// A list of supported policy stages. +pub enum PolicyStage { /// Policies that are evaluated before resolution. BeforeResolution, /// Policies that are evaluated after resolution. AfterResolution, } -impl Display for PolicyPackage { - /// Returns the name of the policy package. +impl Display for PolicyStage { + /// Returns the name of the policy stage. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - PolicyPackage::BeforeResolution => { + PolicyStage::BeforeResolution => { write!(f, "before_resolution") } - PolicyPackage::AfterResolution => { + PolicyStage::AfterResolution => { write!(f, "after_resolution") } } @@ -273,11 +273,11 @@ impl Engine { } /// Returns a list of violations based on the policies, the data, the - /// input, and the given package. - pub fn check(&mut self, package: PolicyPackage) -> Result, Error> { + /// input, and the given policy stage. + pub fn check(&mut self, stage: PolicyStage) -> Result, Error> { let value = self .engine - .eval_rule(format!("data.{}.deny", package)) + .eval_rule(format!("data.{}.deny", stage)) .map_err(|e| Error::ViolationEvaluationError { error: e.to_string(), })?; @@ -306,7 +306,7 @@ mod tests { use weaver_common::error::format_errors; use crate::violation::Violation; - use crate::{Engine, Error, PolicyPackage}; + use crate::{Engine, Error, PolicyStage}; #[test] fn test_policy() -> Result<(), Box> { @@ -345,7 +345,7 @@ mod tests { .map(|v| (v.id().to_owned(), v)) .collect(); - let violations = engine.check(PolicyPackage::BeforeResolution)?; + let violations = engine.check(PolicyStage::BeforeResolution)?; assert_eq!(violations.len(), 3); for violation in violations { @@ -381,7 +381,7 @@ mod tests { let new_semconv: Value = serde_yaml::from_str(&new_semconv).unwrap(); engine.set_input(&new_semconv).unwrap(); - let result = engine.check(PolicyPackage::BeforeResolution); + let result = engine.check(PolicyStage::BeforeResolution); assert!(result.is_err()); let observed_errors = format_errors(&[result.unwrap_err()]); @@ -430,7 +430,7 @@ mod tests { .map(|v| (v.id().to_owned(), v)) .collect(); - let violations = engine.check(PolicyPackage::BeforeResolution)?; + let violations = engine.check(PolicyStage::BeforeResolution)?; assert_eq!(violations.len(), 3); for violation in violations { diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index e908264d..5f4492ac 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -335,8 +335,8 @@ impl TemplateEngine { handle_errors(errs) } - #[allow(clippy::print_stdout)] // This is a CLI tool, so it's fine to print to stdout - #[allow(clippy::print_stderr)] // This is a CLI tool, so it's fine to print to stderr + #[allow(clippy::print_stdout)] // This is used for the OutputDirective::Stdout variant + #[allow(clippy::print_stderr)] // This is used for the OutputDirective::Stderr variant fn evaluate_template( &self, log: impl Logger + Clone + Sync, @@ -345,6 +345,7 @@ impl TemplateEngine { output_directive: &OutputDirective, output_dir: &Path, ) -> Result<(), Error> { + // By default, the file name is the template file name without the extension ".j2" let file_name = template_path .file_name() .unwrap_or_default() diff --git a/deny.toml b/deny.toml index 45a1822f..6bc9b359 100644 --- a/deny.toml +++ b/deny.toml @@ -50,14 +50,9 @@ allow = [ "CC0-1.0", "ISC", "OpenSSL", -# "zlib-acknowledgement", "Zlib", ] -#exceptions = [ -# { allow = [ -# "Unlicense", -# ], name = "measure_time" }, -#] + # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 8bfe15c3..7114291d 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -12,7 +12,7 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use check::RegistryCheckArgs; use weaver_cache::Cache; use weaver_checker::Error::{InvalidPolicyFile, PolicyViolation}; -use weaver_checker::{Engine, Error, PolicyPackage}; +use weaver_checker::{Engine, Error, PolicyStage}; use weaver_common::diagnostic::DiagnosticMessages; use weaver_common::error::handle_errors; use weaver_common::Logger; @@ -287,7 +287,7 @@ pub fn check_policy( let mut errors = vec![]; match policy_engine.set_input(semconv) { - Ok(_) => match policy_engine.check(PolicyPackage::BeforeResolution) { + Ok(_) => match policy_engine.check(PolicyStage::BeforeResolution) { Ok(violations) => { for violation in violations { errors.push(PolicyViolation { From ae7eb93340f42f435f793f8f4a6cefd5c9366ce3 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Sun, 19 May 2024 22:08:13 -0700 Subject: [PATCH 15/17] chore: Clean-up before to merge the PR --- Cargo.lock | 32 +- .../weaver_forge/src/extensions/ansi_code.rs | 441 ------------------ crates/weaver_forge/src/extensions/case.rs | 24 +- crates/weaver_forge/src/extensions/mod.rs | 2 +- .../src/extensions/{acronym.rs => util.rs} | 44 +- crates/weaver_forge/src/lib.rs | 66 +-- 6 files changed, 84 insertions(+), 525 deletions(-) delete mode 100644 crates/weaver_forge/src/extensions/ansi_code.rs rename crates/weaver_forge/src/extensions/{acronym.rs => util.rs} (52%) diff --git a/Cargo.lock b/Cargo.lock index 5e150198..855ba6d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a4bd113ab6da4cd0f521068a6e2ee1065eab54107266a11835d02c8ec86a37" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arc-swap" @@ -284,9 +284,9 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "cc" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -512,9 +512,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -788,9 +788,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -2005,9 +2005,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -2554,9 +2554,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] @@ -3146,9 +3146,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.64" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", diff --git a/crates/weaver_forge/src/extensions/ansi_code.rs b/crates/weaver_forge/src/extensions/ansi_code.rs deleted file mode 100644 index 14454ab4..00000000 --- a/crates/weaver_forge/src/extensions/ansi_code.rs +++ /dev/null @@ -1,441 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Set of filters used to add style and color to the console. - -use minijinja::Value; - -/// Converts the input value into a text with a black foreground color. -#[must_use] -pub(crate) fn black(input: &Value) -> String { - format!("\x1b[30m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a red foreground color. -#[must_use] -pub(crate) fn red(input: &Value) -> String { - format!("\x1b[31m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a green foreground color. -#[must_use] -pub(crate) fn green(input: &Value) -> String { - format!("\x1b[32m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a yellow foreground color. -#[must_use] -pub(crate) fn yellow(input: &Value) -> String { - format!("\x1b[33m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a blue foreground color. -#[must_use] -pub(crate) fn blue(input: &Value) -> String { - format!("\x1b[34m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a magenta foreground color. -#[must_use] -pub(crate) fn magenta(input: &Value) -> String { - format!("\x1b[35m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a cyan foreground color. -#[must_use] -pub(crate) fn cyan(input: &Value) -> String { - format!("\x1b[36m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a white foreground color. -#[must_use] -pub(crate) fn white(input: &Value) -> String { - format!("\x1b[37m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright black foreground color. -#[must_use] -pub(crate) fn bright_black(input: &Value) -> String { - format!("\x1b[90m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright red foreground color. -#[must_use] -pub(crate) fn bright_red(input: &Value) -> String { - format!("\x1b[91m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright green foreground color. -#[must_use] -pub(crate) fn bright_green(input: &Value) -> String { - format!("\x1b[92m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright yellow foreground color. -#[must_use] -pub(crate) fn bright_yellow(input: &Value) -> String { - format!("\x1b[93m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright blue foreground color. -#[must_use] -pub(crate) fn bright_blue(input: &Value) -> String { - format!("\x1b[94m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright magenta foreground color. -#[must_use] -pub(crate) fn bright_magenta(input: &Value) -> String { - format!("\x1b[95m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright cyan foreground color. -#[must_use] -pub(crate) fn bright_cyan(input: &Value) -> String { - format!("\x1b[96m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright white foreground color. -#[must_use] -pub(crate) fn bright_white(input: &Value) -> String { - format!("\x1b[97m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a black background color. -#[must_use] -pub(crate) fn bg_black(input: &Value) -> String { - format!("\x1b[40m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a red background color. -#[must_use] -pub(crate) fn bg_red(input: &Value) -> String { - format!("\x1b[41m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a green background color. -#[must_use] -pub(crate) fn bg_green(input: &Value) -> String { - format!("\x1b[42m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a yellow background color. -#[must_use] -pub(crate) fn bg_yellow(input: &Value) -> String { - format!("\x1b[43m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a blue background color. -#[must_use] -pub(crate) fn bg_blue(input: &Value) -> String { - format!("\x1b[44m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a magenta background color. -#[must_use] -pub(crate) fn bg_magenta(input: &Value) -> String { - format!("\x1b[45m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a cyan background color. -#[must_use] -pub(crate) fn bg_cyan(input: &Value) -> String { - format!("\x1b[46m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a white background color. -#[must_use] -pub(crate) fn bg_white(input: &Value) -> String { - format!("\x1b[47m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright black background color. -#[must_use] -pub(crate) fn bg_bright_black(input: &Value) -> String { - format!("\x1b[100m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright red background color. -#[must_use] -pub(crate) fn bg_bright_red(input: &Value) -> String { - format!("\x1b[101m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright green background color. -#[must_use] -pub(crate) fn bg_bright_green(input: &Value) -> String { - format!("\x1b[102m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright yellow background color. -#[must_use] -pub(crate) fn bg_bright_yellow(input: &Value) -> String { - format!("\x1b[103m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright blue background color. -#[must_use] -pub(crate) fn bg_bright_blue(input: &Value) -> String { - format!("\x1b[104m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright magenta background color. -#[must_use] -pub(crate) fn bg_bright_magenta(input: &Value) -> String { - format!("\x1b[105m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright cyan background color. -#[must_use] -pub(crate) fn bg_bright_cyan(input: &Value) -> String { - format!("\x1b[106m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bright white background color. -#[must_use] -pub(crate) fn bg_bright_white(input: &Value) -> String { - format!("\x1b[107m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a bold style. -#[must_use] -pub(crate) fn bold(input: &Value) -> String { - format!("\x1b[1m{}\x1b[0m", input) -} - -/// Converts the input value into a text with an italic style. -#[must_use] -pub(crate) fn italic(input: &Value) -> String { - format!("\x1b[3m{}\x1b[0m", input) -} - -/// Converts the input value into a text with an underline style. -#[must_use] -pub(crate) fn underline(input: &Value) -> String { - format!("\x1b[4m{}\x1b[0m", input) -} - -/// Converts the input value into a text with a strikethrough style. -#[must_use] -pub(crate) fn strikethrough(input: &Value) -> String { - format!("\x1b[9m{}\x1b[0m", input) -} - -/// Adds all the ANSI filters to the given environment. -pub(crate) fn add_ansi_filters(env: &mut minijinja::Environment<'_>) { - env.add_filter("black", black); - env.add_filter("red", red); - env.add_filter("green", green); - env.add_filter("yellow", yellow); - env.add_filter("blue", blue); - env.add_filter("magenta", magenta); - env.add_filter("cyan", cyan); - env.add_filter("white", white); - - env.add_filter("bright_black", bright_black); - env.add_filter("bright_red", bright_red); - env.add_filter("bright_green", bright_green); - env.add_filter("bright_yellow", bright_yellow); - env.add_filter("bright_blue", bright_blue); - env.add_filter("bright_magenta", bright_magenta); - env.add_filter("bright_cyan", bright_cyan); - env.add_filter("bright_white", bright_white); - - env.add_filter("bg_black", bg_black); - env.add_filter("bg_red", bg_red); - env.add_filter("bg_green", bg_green); - env.add_filter("bg_yellow", bg_yellow); - env.add_filter("bg_blue", bg_blue); - env.add_filter("bg_magenta", bg_magenta); - env.add_filter("bg_cyan", bg_cyan); - env.add_filter("bg_white", bg_white); - - env.add_filter("bg_bright_black", bg_bright_black); - env.add_filter("bg_bright_red", bg_bright_red); - env.add_filter("bg_bright_green", bg_bright_green); - env.add_filter("bg_bright_yellow", bg_bright_yellow); - env.add_filter("bg_bright_blue", bg_bright_blue); - env.add_filter("bg_bright_magenta", bg_bright_magenta); - env.add_filter("bg_bright_cyan", bg_bright_cyan); - env.add_filter("bg_bright_white", bg_bright_white); - - env.add_filter("bold", bold); - env.add_filter("italic", italic); - env.add_filter("underline", underline); - env.add_filter("strikethrough", strikethrough); -} - -#[cfg(test)] -mod tests { - use crate::extensions::ansi_code::add_ansi_filters; - use minijinja::Environment; - - #[test] - fn test_ansi_filters() { - let mut env = Environment::new(); - let ctx = serde_json::Value::Null; - - add_ansi_filters(&mut env); - - assert_eq!( - env.render_str("{{ 'test' | black }}", &ctx).unwrap(), - "\x1b[30mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | red }}", &ctx).unwrap(), - "\x1b[31mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | green }}", &ctx).unwrap(), - "\x1b[32mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | yellow }}", &ctx).unwrap(), - "\x1b[33mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | blue }}", &ctx).unwrap(), - "\x1b[34mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | magenta }}", &ctx).unwrap(), - "\x1b[35mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | cyan }}", &ctx).unwrap(), - "\x1b[36mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | white }}", &ctx).unwrap(), - "\x1b[37mtest\x1b[0m" - ); - - assert_eq!( - env.render_str("{{ 'test' | bright_black }}", &ctx).unwrap(), - "\x1b[90mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bright_red }}", &ctx).unwrap(), - "\x1b[91mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bright_green }}", &ctx).unwrap(), - "\x1b[92mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bright_yellow }}", &ctx) - .unwrap(), - "\x1b[93mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bright_blue }}", &ctx).unwrap(), - "\x1b[94mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bright_magenta }}", &ctx) - .unwrap(), - "\x1b[95mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bright_cyan }}", &ctx).unwrap(), - "\x1b[96mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bright_white }}", &ctx).unwrap(), - "\x1b[97mtest\x1b[0m" - ); - - assert_eq!( - env.render_str("{{ 'test' | bg_black }}", &ctx).unwrap(), - "\x1b[40mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_red }}", &ctx).unwrap(), - "\x1b[41mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_green }}", &ctx).unwrap(), - "\x1b[42mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_yellow }}", &ctx).unwrap(), - "\x1b[43mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_blue }}", &ctx).unwrap(), - "\x1b[44mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_magenta }}", &ctx).unwrap(), - "\x1b[45mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_cyan }}", &ctx).unwrap(), - "\x1b[46mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_white }}", &ctx).unwrap(), - "\x1b[47mtest\x1b[0m" - ); - - assert_eq!( - env.render_str("{{ 'test' | bg_bright_black }}", &ctx) - .unwrap(), - "\x1b[100mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_bright_red }}", &ctx) - .unwrap(), - "\x1b[101mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_bright_green }}", &ctx) - .unwrap(), - "\x1b[102mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_bright_yellow }}", &ctx) - .unwrap(), - "\x1b[103mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_bright_blue }}", &ctx) - .unwrap(), - "\x1b[104mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_bright_magenta }}", &ctx) - .unwrap(), - "\x1b[105mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_bright_cyan }}", &ctx) - .unwrap(), - "\x1b[106mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | bg_bright_white }}", &ctx) - .unwrap(), - "\x1b[107mtest\x1b[0m" - ); - - assert_eq!( - env.render_str("{{ 'test' | bold }}", &ctx).unwrap(), - "\x1b[1mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | italic }}", &ctx).unwrap(), - "\x1b[3mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | underline }}", &ctx).unwrap(), - "\x1b[4mtest\x1b[0m" - ); - assert_eq!( - env.render_str("{{ 'test' | strikethrough }}", &ctx) - .unwrap(), - "\x1b[9mtest\x1b[0m" - ); - - assert_eq!(env.render_str("{{ 'test' | black | bg_white | bold | italic | underline | strikethrough }}", &ctx).unwrap(), "\u{1b}[9m\u{1b}[4m\u{1b}[3m\u{1b}[1m\u{1b}[47m\u{1b}[30mtest\u{1b}[0m\u{1b}[0m\u{1b}[0m\u{1b}[0m\u{1b}[0m\u{1b}[0m"); - } -} diff --git a/crates/weaver_forge/src/extensions/case.rs b/crates/weaver_forge/src/extensions/case.rs index 390b39b0..54e4b868 100644 --- a/crates/weaver_forge/src/extensions/case.rs +++ b/crates/weaver_forge/src/extensions/case.rs @@ -2,11 +2,11 @@ //! Case converter filters used by the template engine. -use crate::config::CaseConvention; +use crate::config::{CaseConvention, TargetConfig}; use minijinja::Environment; /// Add case converter filters to the environment. -pub(crate) fn add_filters(env: &mut Environment<'_>) { +pub(crate) fn add_filters(env: &mut Environment<'_>, target_config: &TargetConfig) { 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)); @@ -23,6 +23,22 @@ pub(crate) fn add_filters(env: &mut Environment<'_>) { case_converter(CaseConvention::ScreamingKebabCase), ); env.add_filter("capitalize_first", capitalize_first); + + // ToDo Do we keep these filters? + env.add_filter("file_name", case_converter(target_config.file_name.clone())); + env.add_filter( + "function_name", + case_converter(target_config.function_name.clone()), + ); + env.add_filter("arg_name", case_converter(target_config.arg_name.clone())); + env.add_filter( + "struct_name", + case_converter(target_config.struct_name.clone()), + ); + env.add_filter( + "field_name", + case_converter(target_config.field_name.clone()), + ); } /// Converts input string to the specified case convention. @@ -104,15 +120,17 @@ fn capitalize_first(input: &str) -> String { #[cfg(test)] mod tests { + use crate::config::TargetConfig; use crate::extensions::case::add_filters; use minijinja::Environment; #[test] fn test_case_converter() { + let target_config = TargetConfig::default(); let mut env = Environment::new(); let ctx = serde_json::Value::Null; - add_filters(&mut env); + add_filters(&mut env, &target_config); assert_eq!( env.render_str("{{ 'Hello World' | lower_case }}", &ctx) diff --git a/crates/weaver_forge/src/extensions/mod.rs b/crates/weaver_forge/src/extensions/mod.rs index 15b8e5bc..1f989a1b 100644 --- a/crates/weaver_forge/src/extensions/mod.rs +++ b/crates/weaver_forge/src/extensions/mod.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 //! Custom filters used by the template engine. -pub mod acronym; pub mod ansi; pub mod case; pub mod code; pub mod otel; +pub mod util; diff --git a/crates/weaver_forge/src/extensions/acronym.rs b/crates/weaver_forge/src/extensions/util.rs similarity index 52% rename from crates/weaver_forge/src/extensions/acronym.rs rename to crates/weaver_forge/src/extensions/util.rs index fbc4d773..42f3b71f 100644 --- a/crates/weaver_forge/src/extensions/acronym.rs +++ b/crates/weaver_forge/src/extensions/util.rs @@ -1,11 +1,49 @@ // SPDX-License-Identifier: Apache-2.0 -//! Acronym filter. +//! Set of utility filters and tests used by the Weaver project. +use crate::config::TargetConfig; +use minijinja::{Environment, ErrorKind, Value}; use regex::Regex; use std::collections::HashMap; use std::sync::OnceLock; +/// Add utility filters and tests to the environment. +pub(crate) fn add_filters(env: &mut Environment<'_>, target_config: &TargetConfig) { + env.add_filter("acronym", acronym(target_config.acronyms.clone())); + env.add_filter("flatten", flatten); + env.add_filter("split_id", split_id); +} + +// Helper filter to work around lack of `list.append()` support in minijinja. +// Will take a list of lists and return a new list containing only elements of sublists. +fn flatten(value: Value) -> Result { + let mut result = Vec::new(); + for sublist in value.try_iter()? { + for item in sublist.try_iter()? { + result.push(item); + } + } + Ok(Value::from(result)) +} + +// Helper function to take an "id" and split it by '.' into namespaces. +fn split_id(value: Value) -> Result, minijinja::Error> { + match value.as_str() { + Some(id) => { + let values: Vec = id + .split('.') + .map(|s| Value::from_safe_string(s.to_owned())) + .collect(); + Ok(values) + } + None => Err(minijinja::Error::new( + ErrorKind::InvalidOperation, + format!("Expected string, found: {value}"), + )), + } +} + /// Create a filter that replaces acronyms in the input string with the full /// name defined in the `acronyms` list. /// @@ -18,10 +56,10 @@ use std::sync::OnceLock; /// # Example /// /// ```rust -/// use weaver_forge::extensions::acronym; +/// use weaver_forge::extensions::util; /// /// let acronyms = vec!["iOS".to_owned(), "API".to_owned(), "URL".to_owned()]; -/// let filter = acronym::acronym(acronyms); +/// let filter = util::acronym(acronyms); /// /// assert_eq!(filter("This is an - IOS - device!"), "This is an - iOS - device!"); /// assert_eq!(filter("This is another type of api with the following url! "), "This is another type of API with the following URL! "); diff --git a/crates/weaver_forge/src/lib.rs b/crates/weaver_forge/src/lib.rs index 5bc1d4b6..96ed97a5 100644 --- a/crates/weaver_forge/src/lib.rs +++ b/crates/weaver_forge/src/lib.rs @@ -27,9 +27,7 @@ use weaver_common::Logger; use crate::config::{ApplicationMode, TargetConfig}; use crate::debug::error_summary; use crate::error::Error::InvalidConfigFile; -use crate::extensions::acronym::acronym; -use crate::extensions::case::case_converter; -use crate::extensions::{ansi, case, code, otel}; +use crate::extensions::{ansi, case, code, otel, util}; use crate::registry::{TemplateGroup, TemplateRegistry}; mod config; @@ -429,35 +427,9 @@ impl TemplateEngine { code::add_filters(&mut env, &self.target_config); ansi::add_filters(&mut env); - case::add_filters(&mut env); + case::add_filters(&mut env, &self.target_config); otel::add_tests_and_filters(&mut env); - - // ToDo These filters are now deprecated and should be removed soon. - env.add_filter( - "file_name", - case_converter(self.target_config.file_name.clone()), - ); - env.add_filter( - "function_name", - case_converter(self.target_config.function_name.clone()), - ); - env.add_filter( - "arg_name", - case_converter(self.target_config.arg_name.clone()), - ); - env.add_filter( - "struct_name", - case_converter(self.target_config.struct_name.clone()), - ); - env.add_filter( - "field_name", - case_converter(self.target_config.field_name.clone()), - ); - - env.add_filter("flatten", flatten); - env.add_filter("split_id", split_id); - - env.add_filter("acronym", acronym(self.target_config.acronyms.clone())); + util::add_filters(&mut env, &self.target_config); Ok(env) } @@ -544,35 +516,6 @@ fn safe_join(root: &Path, template: &str) -> Result { } } -// Helper filter to work around lack of `list.append()` support in minijinja. -// Will take a list of lists and return a new list containing only elements of sublists. -fn flatten(value: Value) -> Result { - let mut result = Vec::new(); - for sublist in value.try_iter()? { - for item in sublist.try_iter()? { - result.push(item); - } - } - Ok(Value::from(result)) -} - -// Helper function to take an "id" and split it by '.' into namespaces. -fn split_id(value: Value) -> Result, minijinja::Error> { - match value.as_str() { - Some(id) => { - let values: Vec = id - .split('.') - .map(|s| Value::from_safe_string(s.to_owned())) - .collect(); - Ok(values) - } - None => Err(minijinja::Error::new( - ErrorKind::InvalidOperation, - format!("Expected string, found: {value}"), - )), - } -} - #[cfg(test)] mod tests { use std::collections::HashSet; @@ -589,6 +532,7 @@ mod tests { use crate::config::{ApplicationMode, CaseConvention, TemplateConfig}; use crate::debug::print_dedup_errors; + use crate::extensions::case::case_converter; use crate::filter::Filter; use crate::registry::TemplateRegistry; use crate::OutputDirective; @@ -710,7 +654,7 @@ mod tests { ]; for test_case in test_cases { - let result = super::case_converter(test_case.case)(test_case.input); + let result = case_converter(test_case.case)(test_case.input); assert_eq!(result, test_case.expected); } } From 5b064f2144029278c43b4e715408cf1b9c08453b Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Mon, 20 May 2024 08:17:34 -0700 Subject: [PATCH 16/17] chore: Replace many map_err by a From for Error. --- Cargo.lock | 4 +- crates/weaver_semconv_gen/src/gen.rs | 178 +++++++++++---------------- crates/weaver_semconv_gen/src/lib.rs | 8 +- 3 files changed, 83 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 855ba6d7..f50b028b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,9 +453,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "58ebf8d6963185c7625d2c3c3962d99eb8936637b1427536d21dc36ae402ebad" dependencies = [ "cfg-if", ] diff --git a/crates/weaver_semconv_gen/src/gen.rs b/crates/weaver_semconv_gen/src/gen.rs index 7c98e67c..c1c86a1a 100644 --- a/crates/weaver_semconv_gen/src/gen.rs +++ b/crates/weaver_semconv_gen/src/gen.rs @@ -2,9 +2,10 @@ //! Markdown writing utilities. -use crate::{Error, GenerateMarkdownArgs, ResolvedSemconvRegistry}; -use itertools::Itertools; use std::fmt::Write; + +use itertools::Itertools; + use weaver_resolved_schema::attribute::Attribute; use weaver_resolved_schema::registry::Group; use weaver_semconv::attribute::{ @@ -14,6 +15,8 @@ use weaver_semconv::attribute::{ use weaver_semconv::group::{GroupType, InstrumentSpec}; use weaver_semconv::stability::Stability; +use crate::{Error, GenerateMarkdownArgs, ResolvedSemconvRegistry}; + // The size a string is allowed to be before it is pushed into notes. const BREAK_COUNT: usize = 50; @@ -36,9 +39,7 @@ impl GenerateMarkdownContext { /// Renders stored notes into markdown format. fn write_rendered_notes(&self, out: &mut Out) -> Result<(), Error> { for (counter, note) in self.notes.iter().enumerate() { - write!(out, "\n**[{}]:** {}\n", counter + 1, note.trim()) - .map_err(|e| Error::StdIoError(e.to_string())) - .map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "\n**[{}]:** {}\n", counter + 1, note.trim())?; } Ok(()) } @@ -64,9 +65,9 @@ fn write_example_list( let mut first = true; for e in list { if !first { - write!(out, "; ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "; ")?; } - write!(out, "`{e}`").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "`{e}`")?; first = false; } Ok(()) @@ -74,18 +75,10 @@ fn write_example_list( fn write_examples_string(out: &mut Out, examples: &Examples) -> Result<(), Error> { match examples { - Examples::Bool(value) => { - Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) - } - Examples::Int(value) => { - Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) - } - Examples::Double(value) => { - Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) - } - Examples::String(value) => { - Ok(write!(out, "`{value}`").map_err(|e| Error::StdIoError(e.to_string()))?) - } + Examples::Bool(value) => Ok(write!(out, "`{value}`")?), + Examples::Int(value) => Ok(write!(out, "`{value}`")?), + Examples::Double(value) => Ok(write!(out, "`{value}`")?), + Examples::String(value) => Ok(write!(out, "`{value}`")?), Examples::Ints(values) => write_example_list(out, values), Examples::Doubles(values) => write_example_list(out, values), Examples::Bools(values) => write_example_list(out, values), @@ -96,11 +89,11 @@ fn write_examples_string(out: &mut Out, examples: &Examples) -> Resu fn write_enum_value_string(out: &mut Out, value: &ValueSpec) -> Result<(), Error> { match value { ValueSpec::Double(v) => { - write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "`{v}`")?; } - ValueSpec::Int(v) => write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?, + ValueSpec::Int(v) => write!(out, "`{v}`")?, ValueSpec::String(v) => { - write!(out, "`{v}`").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "`{v}`")?; } } Ok(()) @@ -113,7 +106,7 @@ fn write_enum_examples_string( let mut first = true; for entry in members.iter().take(3) { if !first { - write!(out, "; ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "; ")?; } write_enum_value_string(out, &entry.value)?; first = false; @@ -129,18 +122,15 @@ fn write_stability_badge( Some(Stability::Stable) => write!( out, "![Stable](https://img.shields.io/badge/-stable-lightgreen)" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?, + )?, Some(Stability::Deprecated) => write!( out, "![Deprecated](https://img.shields.io/badge/-deprecated-red)" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?, + )?, Some(Stability::Experimental) | None => write!( out, "![Experimental](https://img.shields.io/badge/-experimental-blue)" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?, + )?, } Ok(()) } @@ -153,10 +143,8 @@ struct AttributeView<'a> { impl<'a> AttributeView<'a> { fn write_name(&self, out: &mut T) -> Result<(), Error> { match &self.attribute.r#type { - AttributeType::Template(_) => Ok(write!(out, "{}.", self.attribute.name) - .map_err(|e| Error::StdIoError(e.to_string()))?), - _ => Ok(write!(out, "{}", self.attribute.name) - .map_err(|e| Error::StdIoError(e.to_string()))?), + AttributeType::Template(_) => Ok(write!(out, "{}.", self.attribute.name)?), + _ => Ok(write!(out, "{}", self.attribute.name)?), } } @@ -180,11 +168,11 @@ impl<'a> AttributeView<'a> { ) -> Result<(), Error> { match attribute_registry_base_url { Some(prefix) => { - write!(out, "[`").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "[`")?; self.write_name(out)?; - write!(out, "`](").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "`](")?; self.write_registry_link(out, prefix)?; - write!(out, ")").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, ")")?; } None => self.write_name(out)?, } @@ -208,29 +196,27 @@ impl<'a> AttributeView<'a> { write!( out, "\n| Value | Description | Stability |\n|---|---|---|\n" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; if let AttributeType::Enum { members, .. } = &self.attribute.r#type { for m in members { - write!(out, "| ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "| ")?; write_enum_value_string(out, &m.value)?; - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; if let Some(v) = m.brief.as_ref() { - write!(out, "{}", v.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "{}", v.trim())?; } else { // Use the id as the description if missing a brief. - write!(out, "{}", m.id).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "{}", m.id)?; } // Stability. - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; if let Some(note) = m.deprecated.as_ref() { write_stability_badge(out, &Some(Stability::Deprecated))?; - write!(out, "
{}", note.trim()) - .map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "
{}", note.trim())?; } else { write_stability_badge(out, &m.stability)?; } - writeln!(out, " |").map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, " |")?; } } // TODO - error message on not enum?... @@ -260,7 +246,7 @@ impl<'a> AttributeView<'a> { } fn write_type_string(&self, out: &mut Out) -> Result<(), Error> { - write!(out, "{}", self.type_string()).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "{}", self.type_string())?; Ok(()) } @@ -270,8 +256,7 @@ impl<'a> AttributeView<'a> { ctx: &mut GenerateMarkdownContext, ) -> Result<(), Error> { if self.attribute.note.is_empty() { - write!(out, "{}", self.attribute.brief.trim()) - .map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "{}", self.attribute.brief.trim())?; Ok(()) } else { write!( @@ -279,8 +264,7 @@ impl<'a> AttributeView<'a> { "{} {}", self.attribute.brief.trim(), ctx.add_note(self.attribute.note.clone()) - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; Ok(()) } } @@ -292,13 +276,13 @@ impl<'a> AttributeView<'a> { ) -> Result<(), Error> { match &self.attribute.requirement_level { RequirementLevel::Basic(BasicRequirementLevelSpec::Required) => { - Ok(write!(out, "`Required`").map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Required`")?) } RequirementLevel::Basic(BasicRequirementLevelSpec::Recommended) => { - Ok(write!(out, "`Recommended`").map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Recommended`")?) } RequirementLevel::Basic(BasicRequirementLevelSpec::OptIn) => { - Ok(write!(out, "`Opt-In`").map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Opt-In`")?) } RequirementLevel::ConditionallyRequired { text } => { if text.len() > BREAK_COUNT { @@ -306,20 +290,16 @@ impl<'a> AttributeView<'a> { out, "`Conditionally Required` {}", ctx.add_note(text.clone()) - ) - .map_err(|e| Error::StdIoError(e.to_string()))?) + )?) } else { - Ok(write!(out, "`Conditionally Required` {text}") - .map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Conditionally Required` {text}")?) } } RequirementLevel::Recommended { text } => { if text.len() > BREAK_COUNT { - Ok(write!(out, "`Recommended` {}", ctx.add_note(text.clone())) - .map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Recommended` {}", ctx.add_note(text.clone()))?) } else { - Ok(write!(out, "`Recommended` {text}") - .map_err(|e| Error::StdIoError(e.to_string()))?) + Ok(write!(out, "`Recommended` {text}")?) } } } @@ -342,7 +322,7 @@ impl<'a> AttributeView<'a> { fn write_stability(&self, out: &mut Out) -> Result<(), Error> { if let Some(note) = self.attribute.deprecated.as_ref() { write_stability_badge(out, &Some(Stability::Deprecated))?; - write!(out, "
{}", note.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "
{}", note.trim())?; } else { write_stability_badge(out, &self.attribute.stability)?; } @@ -433,43 +413,38 @@ impl<'a> AttributeTableView<'a> { } if self.group.r#type == GroupType::Event { - write!(out, "The event name MUST be `{}`\n\n", self.event_name()) - .map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "The event name MUST be `{}`\n\n", self.event_name())?; } if args.is_omit_requirement() { writeln!( out, "| Attribute | Type | Description | Examples | Stability |" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; - writeln!(out, "|---|---|---|---|---|") - .map_err(|e| Error::StdIoError(e.to_string())) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; + writeln!(out, "|---|---|---|---|---|")?; } else { - writeln!(out, "| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |").map_err(|e| Error::StdIoError(e.to_string()))?; - writeln!(out, "|---|---|---|---|---|---|") - .map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, "| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |")?; + writeln!(out, "|---|---|---|---|---|---|")?; } for attr in &attributes { - write!(out, "| ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "| ")?; attr.write_name_with_optional_link(out, attribute_registry_base_url)?; - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; attr.write_type_string(out)?; - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; attr.write_description(out, ctx)?; - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; attr.write_examples(out)?; if args.is_omit_requirement() { - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; } else { - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; attr.write_requirement(out, ctx)?; - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; } attr.write_stability(out)?; - writeln!(out, " |").map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, " |")?; } // Add "note" footers ctx.write_rendered_notes(out)?; @@ -485,25 +460,23 @@ impl<'a> AttributeTableView<'a> { write!( out, "\nThe following attributes can be important for making sampling decisions " - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; write!( out, "and SHOULD be provided **at span creation time** (if provided at all):\n\n" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; for a in sampling_relevant { - write!(out, "* ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "* ")?; a.write_name_with_optional_link(out, attribute_registry_base_url)?; - writeln!(out).map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out)?; } } // Add enum footers for e in attributes.iter().filter(|a| a.is_enum()) { - write!(out, "\n`").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "\n`")?; e.write_name(out)?; - writeln!(out, "` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used.").map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, "` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used.")?; e.write_enum_spec_table(out)?; } Ok(()) @@ -514,6 +487,7 @@ pub struct MetricView<'a> { group: &'a Group, // metric: &'a Metric, } + impl<'a> MetricView<'a> { pub fn try_new(id: &str, lookup: &'a ResolvedSemconvRegistry) -> Result, Error> { let metric = lookup @@ -544,8 +518,8 @@ impl<'a> MetricView<'a> { } fn write_unit(&self, out: &mut Out) -> Result<(), Error> { match self.group.unit.as_ref() { - Some(value) => write!(out, "{value}").map_err(|e| Error::StdIoError(e.to_string()))?, - None => write!(out, "1").map_err(|e| Error::StdIoError(e.to_string()))?, + Some(value) => write!(out, "{value}")?, + None => write!(out, "1")?, } Ok(()) } @@ -556,22 +530,21 @@ impl<'a> MetricView<'a> { ) -> Result<(), Error> { // TODO - add note if needed. if self.group.note.is_empty() { - write!(out, "{}", &self.group.brief).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "{}", &self.group.brief)?; } else { write!( out, "{} {}", &self.group.brief, ctx.add_note(self.group.note.clone()) - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; } Ok(()) } fn write_stability(&self, out: &mut Out) -> Result<(), Error> { if let Some(note) = self.group.deprecated.as_ref() { write_stability_badge(out, &Some(Stability::Deprecated))?; - write!(out, "
{}", note.trim()).map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "
{}", note.trim())?; } else { write_stability_badge(out, &self.group.stability)?; } @@ -585,26 +558,23 @@ impl<'a> MetricView<'a> { writeln!( out, "| Name | Instrument Type | Unit (UCUM) | Description | Stability |" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; writeln!( out, "| -------- | --------------- | ----------- | -------------- | --------- |" - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; write!( out, "| `{}` | {} | `", self.metric_name(), self.instrument() - ) - .map_err(|e| Error::StdIoError(e.to_string()))?; + )?; self.write_unit(out)?; - write!(out, "` | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, "` | ")?; self.write_description(out, ctx)?; - write!(out, " | ").map_err(|e| Error::StdIoError(e.to_string()))?; + write!(out, " | ")?; self.write_stability(out)?; - writeln!(out, " |").map_err(|e| Error::StdIoError(e.to_string()))?; + writeln!(out, " |")?; // Add "note" footers ctx.write_rendered_notes(out)?; Ok(()) diff --git a/crates/weaver_semconv_gen/src/lib.rs b/crates/weaver_semconv_gen/src/lib.rs index db945860..866ef18f 100644 --- a/crates/weaver_semconv_gen/src/lib.rs +++ b/crates/weaver_semconv_gen/src/lib.rs @@ -5,7 +5,7 @@ //! poorly porting the code into RUST. We expect to optimise and improve things over time. use miette::Diagnostic; -use std::fs; +use std::{fmt, fs}; use serde::Serialize; use weaver_cache::Cache; @@ -119,6 +119,12 @@ impl WeaverError for Error { } } +impl From for Error { + fn from(e: fmt::Error) -> Self { + Error::StdIoError(e.to_string()) + } +} + // TODO - this is based on https://github.com/open-telemetry/build-tools/blob/main/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py#L503 // We can likely model this much better. /// Parameters users can specify for generating markdown. From d2343a32635148eaab9c4a93e0c6a8c76eed4073 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Mon, 20 May 2024 13:51:40 -0700 Subject: [PATCH 17/17] chore: Update weaver_checker README.md to introduce PolicyStage --- crates/weaver_checker/README.md | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/crates/weaver_checker/README.md b/crates/weaver_checker/README.md index d21fe265..f208597d 100644 --- a/crates/weaver_checker/README.md +++ b/crates/weaver_checker/README.md @@ -68,6 +68,51 @@ The policy verification process involves: The following diagram illustrates the policy verification process: ![Policy Verification Process](images/policy-verification-process.svg) +## Policy Stages +Policies can be applied at two different stages of the resolution process. +1) To apply policies before the resolution process, simply group the policies +into a package named `before_resolution`. +2) To apply them after the resolution process, the `after_resolution` package +should be used + +The example below presents a set of violation detection rules that will apply +before the validation process. + +```rego +package before_resolution + +deny[attr_registry_violation("registry_with_ref_attr", group.id, attr.ref)] { + group := input.groups[_] + startswith(group.id, "registry.") + attr := group.attributes[_] + attr.ref != null +} + +# An attribute whose stability is not `deprecated` but has the deprecated field +# set to true is invalid. +deny[attr_violation("attr_stability_deprecated", group.id, attr.id)] { + group := input.groups[_] + attr := group.attributes[_] + attr.stability != "deprecaded" + attr.deprecated +} + +# An attribute cannot be removed from a group that has already been released. +deny[schema_evolution_violation("attr_removed", old_group.id, old_attr.id)] { + old_group := data.groups[_] + old_attr := old_group.attributes[_] + not attr_exists_in_new_group(old_group.id, old_attr.id) +} +``` + +> [!NOTE] +> Note 1: The after_resolution stage is not yet fully supported by Weaver. + +> [!NOTE] +> Note 2: An upcoming version of Weaver will also allow applying rules on two +> distinct versions of the registries (before or after resolution). This will +> enable the definition of schema evolution rules. + ### Usage To verify policies, the command `weaver registry check` can be invoked with one or more Rego files as parameters. This allows for the specific context-based