Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file.

# Unreleased

- Fix error messages to ignore new version variants ([#880](https://github.com/open-telemetry/weaver/pull/880) by @jsuereth)

# [0.17.0] - 2025-08-08

- Filter based on deprecation, stability, and annotations in signal JQ helpers
Expand Down
6 changes: 4 additions & 2 deletions crates/weaver_resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,8 @@ impl SchemaResolver {

let local_path = registry_repo.path().to_path_buf();
let registry_path_repr = registry_repo.registry_path_repr();
let validator = JsonSchemaValidator::new();
let versioned_validator = JsonSchemaValidator::new_versioned();
let unversioned_validator = JsonSchemaValidator::new_unversioned();

// Loads the semantic convention specifications from the git repo.
// All yaml files are recursively loaded and parsed in parallel from
Expand All @@ -342,7 +343,8 @@ impl SchemaResolver {
vec![SemConvRegistry::semconv_spec_from_file(
&registry_repo.id(),
entry.path(),
&validator,
&unversioned_validator,
&versioned_validator,
|path| {
// Replace the local path with the git URL combined with the relative path
// of the semantic convention file.
Expand Down
32 changes: 22 additions & 10 deletions crates/weaver_semconv/src/json_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

//! JSON Schema validator for semantic convention files.

use crate::semconv::SemConvSpec;
use crate::semconv::{SemConvSpec, SemConvSpecV1, Versioned};
use crate::Error::{CompoundError, InvalidSemConvSpec, InvalidXPath};
use crate::{Error, InvalidSemConvSpecError};
use itertools::Itertools;
use jsonschema::error::{TypeKind, ValidationErrorKind};
use jsonschema::{JsonType, JsonTypeSet};
use miette::{NamedSource, SourceSpan};
use saphyr::{LoadableYamlNode, MarkedYaml};
use schemars::JsonSchema;
use std::borrow::Cow;

/// Parsed simple XPath components
Expand All @@ -28,25 +29,36 @@ pub struct JsonSchemaValidator {
validator: jsonschema::Validator,
}

impl Default for JsonSchemaValidator {
fn default() -> Self {
Self::new()
}
}

impl JsonSchemaValidator {
/// Creates a new JSON schema validator.
#[must_use]
pub fn new() -> Self {
pub fn new_all_versions() -> Self {
Self::new_for::<SemConvSpec>()
}

/// Creates a new JSON schema validator that ONLY works when `version` is not specified.
#[must_use]
pub fn new_unversioned() -> Self {
Self::new_for::<SemConvSpecV1>()
}

/// Creates a new JSON schema validator that ONLY works when `version` is specified.
#[must_use]
pub fn new_versioned() -> Self {
Self::new_for::<Versioned>()
}

/// Creates a new JSON schema validator that works for any type T.
#[must_use]
fn new_for<T: JsonSchema>() -> Self {
// Generate the JSON schema for the SemConvSpec struct using Schemars
let root_schema = schemars::schema_for!(SemConvSpec);
let root_schema = schemars::schema_for!(T);
let json_schema = serde_json::to_value(&root_schema)
// Should never happen as we expert Schemars to work
.expect("Failed to convert schema to JSON value");
let validator = jsonschema::Validator::new(&json_schema)
// Should never happen as we expert Schemars to work
.expect("Failed to create JSON schema validator");

JsonSchemaValidator {
json_schema,
validator,
Expand Down
12 changes: 8 additions & 4 deletions crates/weaver_semconv/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ impl SemConvRegistry {
non_fatal_errors: &mut Vec<Error>,
) -> Result<SemConvRegistry, Error> {
let mut registry = SemConvRegistry::new(registry_id);
let validator = JsonSchemaValidator::new();
let versioned_validator = JsonSchemaValidator::new_versioned();
let unversioned_validator = JsonSchemaValidator::new_unversioned();
for sc_entry in
glob::glob(path_pattern).map_err(|e| Error::InvalidRegistryPathPattern {
path_pattern: path_pattern.to_owned(),
Expand All @@ -95,7 +96,8 @@ impl SemConvRegistry {
let (semconv_spec, nfes) = SemConvSpecWithProvenance::from_file(
registry_id,
path_buf.as_path(),
&validator,
&unversioned_validator,
&versioned_validator,
)
.into_result_with_non_fatal()?;
registry.add_semconv_spec(semconv_spec);
Expand Down Expand Up @@ -203,7 +205,8 @@ impl SemConvRegistry {
pub fn semconv_spec_from_file<P, F>(
registry_id: &str,
semconv_path: P,
validator: &JsonSchemaValidator,
unversioned_validator: &JsonSchemaValidator,
versioned_validator: &JsonSchemaValidator,
path_fixer: F,
) -> WResult<SemConvSpecWithProvenance, Error>
where
Expand All @@ -213,7 +216,8 @@ impl SemConvRegistry {
SemConvSpecWithProvenance::from_file_with_mapped_path(
registry_id,
semconv_path,
validator,
unversioned_validator,
versioned_validator,
path_fixer,
)
}
Expand Down
65 changes: 47 additions & 18 deletions crates/weaver_semconv/src/semconv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,16 @@ impl SemConvSpecWithProvenance {
pub fn from_file<P: AsRef<Path>>(
registry_id: &str,
path: P,
validator: &JsonSchemaValidator,
unversioned_validator: &JsonSchemaValidator,
versioned_validator: &JsonSchemaValidator,
) -> WResult<SemConvSpecWithProvenance, Error> {
Self::from_file_with_mapped_path(registry_id, path, validator, |path| path)
Self::from_file_with_mapped_path(
registry_id,
path,
unversioned_validator,
versioned_validator,
|path| path,
)
}
/// Creates a semantic convention spec with provenance from a file.
///
Expand All @@ -267,7 +274,8 @@ impl SemConvSpecWithProvenance {
pub fn from_file_with_mapped_path<P, F>(
registry_id: &str,
path: P,
validator: &JsonSchemaValidator,
unversioned_validator: &JsonSchemaValidator,
versioned_validator: &JsonSchemaValidator,
path_fixer: F,
) -> WResult<SemConvSpecWithProvenance, Error>
where
Expand All @@ -277,7 +285,8 @@ impl SemConvSpecWithProvenance {
fn from_file_or_fatal(
path: &Path,
provenance: &str,
json_schema_validator: &JsonSchemaValidator,
unversioned_validator: &JsonSchemaValidator,
versioned_validator: &JsonSchemaValidator,
) -> Result<SemConvSpec, Error> {
use serde_yaml::Value;
use std::io::Seek;
Expand All @@ -299,7 +308,18 @@ impl SemConvSpecWithProvenance {
let original_error = e.to_string();
let value: Result<Value, _> = serde_yaml::from_reader(&mut semconv_file);
if let Ok(yaml_value) = value {
json_schema_validator.validate_yaml(yaml_value, provenance, e)?;
// TODO - Check if we should use versioned or unversioned validator.
if yaml_value
.as_mapping()
.and_then(|m| m.get("version"))
.map(|v| v.is_string())
.unwrap_or(false)
{
// Use versioned validator.
versioned_validator.validate_yaml(yaml_value, provenance, e)?;
} else {
unversioned_validator.validate_yaml(yaml_value, provenance, e)?;
}
}

// Fallback: return original serde error
Expand All @@ -312,7 +332,12 @@ impl SemConvSpecWithProvenance {
}
let path = path.as_ref().display().to_string();
let provenance = Provenance::new(registry_id, &path_fixer(path.clone()));
let raw_spec = match from_file_or_fatal(path.as_ref(), &path, validator) {
let raw_spec = match from_file_or_fatal(
path.as_ref(),
&path,
unversioned_validator,
versioned_validator,
) {
Ok(semconv_spec) => {
// Important note: the resolution process expects this step of validation to be done for
// each semantic convention spec.
Expand Down Expand Up @@ -397,26 +422,29 @@ mod tests {

#[test]
fn test_semconv_spec_from_file() {
let validator = JsonSchemaValidator::new();
let validator = JsonSchemaValidator::new_all_versions();
// Existing file
let path = PathBuf::from("data/database.yaml");

let semconv_spec = SemConvSpecWithProvenance::from_file("test", path, &validator)
.into_result_failing_non_fatal()
.unwrap();
let semconv_spec =
SemConvSpecWithProvenance::from_file("test", path, &validator, &validator)
.into_result_failing_non_fatal()
.unwrap();
assert_eq!(semconv_spec.spec.into_v1("test").groups.len(), 10);

// Non-existing file
let path = PathBuf::from("data/non-existing.yaml");
let semconv_spec = SemConvSpecWithProvenance::from_file("test", path, &validator)
.into_result_failing_non_fatal();
let semconv_spec =
SemConvSpecWithProvenance::from_file("test", path, &validator, &validator)
.into_result_failing_non_fatal();
assert!(semconv_spec.is_err());
assert!(matches!(semconv_spec.unwrap_err(), RegistryNotFound { .. }));

// Invalid file structure
let path = PathBuf::from("data/invalid/invalid-semconv.yaml");
let semconv_spec = SemConvSpecWithProvenance::from_file("test", path, &validator)
.into_result_failing_non_fatal();
let semconv_spec =
SemConvSpecWithProvenance::from_file("test", path, &validator, &validator)
.into_result_failing_non_fatal();
assert!(semconv_spec.is_err());
assert!(matches!(
semconv_spec.unwrap_err(),
Expand Down Expand Up @@ -604,11 +632,12 @@ mod tests {

#[test]
fn test_semconv_spec_with_provenance_from_file() {
let validator = JsonSchemaValidator::new();
let validator = JsonSchemaValidator::new_all_versions();
let path = PathBuf::from("data/database.yaml");
let semconv_spec = SemConvSpecWithProvenance::from_file("main", &path, &validator)
.into_result_failing_non_fatal()
.unwrap();
let semconv_spec =
SemConvSpecWithProvenance::from_file("main", &path, &validator, &validator)
.into_result_failing_non_fatal()
.unwrap();
assert_eq!(semconv_spec.spec.into_v1("test").groups.len(), 10);
assert_eq!(semconv_spec.provenance.path, path.display().to_string());
}
Expand Down
Loading