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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .typos.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[files]
extend-exclude = ["*.cast", "**/*.yaml", "**/*.svg", "**/allowed-external-types.toml"]
extend-exclude = ["*.cast", "**/*.yaml", "**/*.json", "**/*.svg", "**/allowed-external-types.toml"]

[default.extend-words]
ratatui = "ratatui"
Expand Down
2 changes: 1 addition & 1 deletion crates/weaver_forge/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ mod tests {
],
};

let catalog = Catalog::from_attributes(vec![]);
let catalog = Catalog::default();

// Convert to resolved registry
let resolved = ResolvedRegistry::try_from_resolved_registry(&registry, &catalog)
Expand Down
99 changes: 69 additions & 30 deletions crates/weaver_resolved_schema/src/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
//! that are shared across multiple signals in the Resolved Telemetry Schema.

use crate::attribute::{Attribute, AttributeRef};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::Serialize;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Debug;
use weaver_semconv::attribute::{AttributeType, BasicRequirementLevelSpec, RequirementLevel};
Expand All @@ -15,13 +14,17 @@ use weaver_semconv::stability::Stability;
/// Attribute references are used to refer to attributes in the catalog.
///
/// Note : In the future, this catalog could be extended with other entities.
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, Default, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[must_use]
pub struct Catalog {
/// Catalog of attributes used in the schema.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attributes: Vec<Attribute>,
/// Catalog of attribute definitions and refinements used in the schema.
/// Contains elements with the same attribute key.
/// Use root_attributes for original attribute definitions.
attributes: Vec<Attribute>,
/// Attribute definitions available in this registry (including those
/// from dependencies). Used for cross-registry attribute lookup.
/// Not serialized — populated only for freshly resolved schemas.
root_attributes: HashMap<String, (Attribute, String)>,
Copy link
Member Author

@lmolkova lmolkova Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the real change for v1, let's populate root_attributes directly instead of trying to use lineage to infer if something in the catalog is original definition.

The rest in this file is cleanup - this struct should not allow to mutate attrs, does not need to be (de)serializable, many of its features were only used in tests

}

/// Statistics on a catalog.
Expand All @@ -41,31 +44,23 @@ pub struct Stats {
}

impl Catalog {
/// Creates a catalog from a list of attributes.
pub fn from_attributes(attributes: Vec<Attribute>) -> Self {
Self { attributes }
}

/// Adds attributes to the catalog and returns a list of attribute references.
#[must_use]
pub fn add_attributes<const N: usize>(
&mut self,
attributes: [Attribute; N],
) -> Vec<AttributeRef> {
let start_index = self.attributes.len();
self.attributes.extend(attributes.iter().cloned());
(start_index..self.attributes.len())
.map(|i| AttributeRef(i as u32))
.collect::<Vec<_>>()
/// Creates a catalog from a list of attributes and root attribute definitions.
pub fn new(
attributes: Vec<Attribute>,
root_attributes: HashMap<String, (Attribute, String)>,
) -> Self {
Self {
attributes,
root_attributes,
}
}

/// Returns the attribute name from an attribute ref if it exists
/// in the catalog or None if it does not exist.
/// Looks up an attribute by name in the root attribute definitions.
#[must_use]
pub fn attribute_name(&self, attribute_ref: &AttributeRef) -> Option<&str> {
self.attributes
.get(attribute_ref.0 as usize)
.map(|attr| attr.name.as_ref())
pub fn root_attribute(&self, name: &str) -> Option<(&Attribute, &str)> {
self.root_attributes
.get(name)
.map(|(attr, group_id)| (attr, group_id.as_str()))
}

/// Counts the number of attributes in the catalog.
Expand All @@ -75,7 +70,7 @@ impl Catalog {
}

/// Return an iterator over the attributes in the catalog.
pub fn iter(&self) -> impl Iterator<Item = &Attribute> {
pub fn attributes(&self) -> impl Iterator<Item = &Attribute> {
self.attributes.iter()
}

Expand Down Expand Up @@ -140,3 +135,47 @@ impl Catalog {
}
}
}

#[cfg(test)]
/// Test utilities for building [`Catalog`] instances.
pub mod test_utils {
use super::*;

/// A builder for constructing a [`Catalog`] in tests.
#[derive(Default)]
pub struct CatalogBuilder {
attributes: Vec<Attribute>,
root_attributes: HashMap<String, (Attribute, String)>,
}

impl CatalogBuilder {
/// Creates a builder pre-populated with all attributes from an existing catalog.
/// Root attributes are not copied — use [`CatalogBuilder::add`] with a `group_id` for that.
#[must_use]
pub fn from_catalog(catalog: &Catalog) -> Self {
let mut builder = Self::default();
for attr in catalog.attributes() {
let _ = builder.add(attr.clone(), None);
}
builder
}

/// Adds an attribute to the catalog. If `group_id` is `Some`, the attribute
/// is also registered as a root definition for cross-registry lookup.
pub fn add(&mut self, attr: Attribute, group_id: Option<&str>) -> AttributeRef {
if let Some(gid) = group_id {
let _ = self
.root_attributes
.insert(attr.name.clone(), (attr.clone(), gid.to_owned()));
}
let idx = self.attributes.len();
self.attributes.push(attr);
AttributeRef(idx as u32)
}

/// Builds the [`Catalog`].
pub fn build(self) -> Catalog {
Catalog::new(self.attributes, self.root_attributes)
}
}
}
33 changes: 13 additions & 20 deletions crates/weaver_resolved_schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::catalog::Catalog;
use crate::instrumentation_library::InstrumentationLibrary;
use crate::registry::{Group, Registry};
use crate::resource::Resource;
use schemars::JsonSchema;
use serde::Serialize;
use std::collections::HashMap;
use weaver_semconv::deprecated::Deprecated;
Expand Down Expand Up @@ -46,8 +45,7 @@ pub(crate) const V2_RESOLVED_FILE_FORMAT: &str = "resolved/2.0.0";
/// A Resolved Telemetry Schema.
/// A Resolved Telemetry Schema is self-contained and doesn't contain any
/// external references to other schemas or semantic conventions.
#[derive(Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
#[derive(Debug)]
pub struct ResolvedTelemetrySchema {
/// Version of the file structure.
pub file_format: String,
Expand All @@ -61,22 +59,18 @@ pub struct ResolvedTelemetrySchema {
/// and signals.
pub catalog: Catalog,
/// Resource definition (only for application).
#[serde(skip_serializing_if = "Option::is_none")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional?

Copy link
Member Author

@lmolkova lmolkova Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, it does not need json schema - we don't (and don't need to) support it for v1. And it's not (de)serializable either

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to remove serialization from things that are not intended to be read or written by weaver. This helps to understand what's internal detail and what's a real contract

pub resource: Option<Resource>,
/// Definition of the instrumentation library for the instrumented application or library.
/// Or none if the resolved telemetry schema represents a semantic convention registry.
#[serde(skip_serializing_if = "Option::is_none")]
pub instrumentation_library: Option<InstrumentationLibrary>,
/// The list of dependencies of the current instrumentation application or library.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub dependencies: Vec<InstrumentationLibrary>,
/// Definitions for each schema version in this family.
/// Note: the ordering of versions is defined according to semver
/// version number ordering rules.
/// This section is described in more details in the OTEP 0152 and in a dedicated
/// section below.
/// <https://github.com/open-telemetry/oteps/blob/main/text/0152-telemetry-schemas.md>
#[serde(skip_serializing_if = "Option::is_none")]
pub versions: Option<Versions>,
/// The manifest of the registry.
pub registry_manifest: Option<RegistryManifest>,
Expand Down Expand Up @@ -117,7 +111,12 @@ impl ResolvedTelemetrySchema {
attrs: [Attribute; N],
deprecated: Option<Deprecated>,
) {
let attr_refs = self.catalog.add_attributes(attrs);
let mut builder = catalog::test_utils::CatalogBuilder::from_catalog(&self.catalog);
let attr_refs: Vec<attribute::AttributeRef> = attrs
.into_iter()
.map(|a| builder.add(a, Some(group_id)))
.collect();
self.catalog = builder.build();
self.registry.groups.push(Group {
id: group_id.to_owned(),
r#type: GroupType::Metric,
Expand Down Expand Up @@ -161,7 +160,12 @@ impl ResolvedTelemetrySchema {
let al = AttributeLineage::new(group_id);
lineage.add_attribute_lineage(attr.name.clone(), al);
}
let attr_refs: Vec<attribute::AttributeRef> = self.catalog.add_attributes(attrs);
let mut builder = catalog::test_utils::CatalogBuilder::from_catalog(&self.catalog);
let attr_refs: Vec<attribute::AttributeRef> = attrs
.into_iter()
.map(|a| builder.add(a, Some(group_id)))
.collect();
self.catalog = builder.build();
self.registry.groups.push(Group {
id: group_id.to_owned(),
r#type: GroupType::AttributeGroup,
Expand Down Expand Up @@ -525,20 +529,9 @@ impl ResolvedTelemetrySchema {
mod tests {
use crate::attribute::Attribute;
use crate::ResolvedTelemetrySchema;
use schemars::schema_for;
use serde_json::to_string_pretty;
use weaver_semconv::deprecated::Deprecated;
use weaver_version::schema_changes::{SchemaItemChange, SchemaItemType};

#[test]
fn test_json_schema_gen() {
// Ensure the JSON schema can be generated for the ResolvedTelemetrySchema
let schema = schema_for!(ResolvedTelemetrySchema);

// Ensure the schema can be serialized to a string
assert!(to_string_pretty(&schema).is_ok());
}

#[test]
fn no_diff() {
let mut prior_schema = ResolvedTelemetrySchema::new("1.0", "", "");
Expand Down
Loading
Loading