From 2d7ad646d4b251646a5f044634bc38e16a7f16d4 Mon Sep 17 00:00:00 2001 From: Nev Wylie <54870357+MSNev@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:00:04 -0700 Subject: [PATCH] Remove allow_custom_values, deprecated and type_display --- crates/weaver_forge/data/mobile-events.yaml | 2 - .../semconv_jq_fn/semconv_events.json | 2 +- .../weaver_resolved_schema/src/any_value.rs | 14 - .../expected-events.json | 8 - .../expected-registry.json | 44 +-- .../registry/mobile-events.yaml | 2 - crates/weaver_resolver/src/any_value.rs | 23 +- crates/weaver_semconv/data/event.yaml | 2 - .../data/expected/any_value.json | 2 +- .../data/expected/any_value.yaml | 1 - crates/weaver_semconv/src/any_value.rs | 98 +------ crates/weaver_semconv/src/attribute.rs | 68 +++++ crates/weaver_semconv/src/group.rs | 250 +++++++++++++++++- crates/weaver_semconv/src/lib.rs | 14 + schemas/semconv-syntax.md | 136 ++++++---- schemas/semconv.schema.json | 141 +++++----- 16 files changed, 522 insertions(+), 285 deletions(-) diff --git a/crates/weaver_forge/data/mobile-events.yaml b/crates/weaver_forge/data/mobile-events.yaml index a8008d98..503b045d 100644 --- a/crates/weaver_forge/data/mobile-events.yaml +++ b/crates/weaver_forge/data/mobile-events.yaml @@ -24,7 +24,6 @@ groups: brief: > This attribute represents the state the application has transitioned into at the occurrence of the event. type: enum - allow_custom_values: false members: - id: active value: 'active' @@ -58,7 +57,6 @@ groups: The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. type: enum - allow_custom_values: false members: - id: created value: 'created' diff --git a/crates/weaver_forge/expected_output/semconv_jq_fn/semconv_events.json b/crates/weaver_forge/expected_output/semconv_jq_fn/semconv_events.json index 2cb781cd..debdb697 100644 --- a/crates/weaver_forge/expected_output/semconv_jq_fn/semconv_events.json +++ b/crates/weaver_forge/expected_output/semconv_jq_fn/semconv_events.json @@ -1 +1 @@ -[{"attributes":[],"body":{"fields":[{"allow_custom_values":false,"brief":"This attribute represents the state the application has transitioned into at the occurrence of the event.\n","members":[{"brief":"The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`.\n","deprecated":null,"id":"active","note":null,"stability":null,"value":"active"},{"brief":"The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`.\n","deprecated":null,"id":"inactive","note":null,"stability":null,"value":"inactive"},{"brief":"The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`.\n","deprecated":null,"id":"background","note":null,"stability":null,"value":"background"},{"brief":"The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`.\n","deprecated":null,"id":"foreground","note":null,"stability":null,"value":"foreground"},{"brief":"The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`.\n","deprecated":null,"id":"terminate","note":null,"stability":null,"value":"terminate"}],"name":"ios.state","note":"The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived.\n","requirement_level":{"conditionally_required":"if and only if `os.name` is `ios`"},"stability":"experimental","type":"enum","type_display":"enum\u003cios.state\u003e {active, inactive, background, foreground, terminate}"},{"allow_custom_values":false,"brief":"This attribute represents the state the application has transitioned into at the occurrence of the event.\n","members":[{"brief":"Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time.\n","deprecated":null,"id":"created","note":null,"stability":null,"value":"created"},{"brief":"Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state.\n","deprecated":null,"id":"background","note":null,"stability":null,"value":"background"},{"brief":"Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states.","deprecated":null,"id":"foreground","note":null,"stability":null,"value":"foreground"}],"name":"android.state","note":"The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived.\n","requirement_level":{"conditionally_required":"if and only if `os.name` is `android`"},"stability":"experimental","type":"enum","type_display":"enum\u003candroid.state\u003e {created, background, foreground}"}],"name":"device.app.lifecycle.fields","requirement_level":"required","type":"map","type_display":"map\u003cdevice.app.lifecycle.fields\u003e{ enum\u003cios.state\u003e {active, inactive, background, foreground, terminate}, enum\u003candroid.state\u003e {created, background, foreground} }"},"brief":"This event represents an occurrence of a lifecycle transition on Android or iOS platform.\n","event_namespace":"device.app","events":[],"id":"device.app.lifecycle","instrument":null,"lineage":{"source_file":"data/mobile-events.yaml"},"metric_name":null,"name":"device.app.lifecycle","note":"This event identifies the fields that are common to all lifecycle events for android and iOS using the `android.state` and `ios.state` fields. The `android.state` and `ios.state` attributes are mutually exclusive.\n","span_kind":null,"stability":"experimental","type":"event","unit":null},{"attributes":[{"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.\n","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)","name":"exception.stacktrace","requirement_level":"recommended","stability":"stable","type":"string"},{"brief":"SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span.\n","name":"exception.escaped","note":"An exception is considered to have escaped (or left) the scope of a span,\nif that span is ended while the exception is still logically \"in flight\".\nThis may be actually \"in flight\" in some languages (e.g. if the exception\nis passed to a Context manager\u0027s `__exit__` method in Python) but will\nusually be caught at the point of recording the exception in most languages.\n\nIt is usually not possible to determine at the point where an exception is thrown\nwhether it will escape the scope of a span.\nHowever, it is trivial to know that an exception\nwill escape, if one checks for an active exception just before ending the span,\nas done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception).\n\nIt follows that an exception may still escape the scope of the span\neven if the `exception.escaped` attribute was not set or set to false,\nsince the event might have been recorded at a time where it was not\nclear whether the exception will escape.","requirement_level":"recommended","stability":"stable","type":"boolean"},{"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.\n","examples":["java.net.ConnectException","OSError"],"name":"exception.type","requirement_level":{"conditionally_required":"Required if `exception.message` is not set, recommended otherwise."},"stability":"stable","type":"string"},{"brief":"The exception message.","examples":["Division by zero","Can\u0027t convert \u0027int\u0027 object to str implicitly"],"name":"exception.message","requirement_level":{"conditionally_required":"Required if `exception.type` is not set, recommended otherwise."},"stability":"stable","type":"string"}],"brief":"This document defines the attributes used to report a single exception associated with a span.\n","event_namespace":"other","events":[],"id":"trace-exception","instrument":null,"lineage":{"attributes":{"exception.escaped":{"inherited_fields":["brief","note","requirement_level","stability"],"source_group":"registry.exception"},"exception.message":{"inherited_fields":["brief","examples","note","stability"],"locally_overridden_fields":["requirement_level"],"source_group":"registry.exception"},"exception.stacktrace":{"inherited_fields":["brief","examples","note","requirement_level","stability"],"source_group":"registry.exception"},"exception.type":{"inherited_fields":["brief","examples","note","stability"],"locally_overridden_fields":["requirement_level"],"source_group":"registry.exception"}},"source_file":"data/trace-exception.yaml"},"metric_name":null,"name":null,"prefix":"exception","span_kind":null,"type":"event","unit":null}] \ No newline at end of file +[{"attributes":[],"body":{"fields":[{"brief":"This attribute represents the state the application has transitioned into at the occurrence of the event.\n","members":[{"brief":"The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`.\n","deprecated":null,"id":"active","note":null,"stability":null,"value":"active"},{"brief":"The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`.\n","deprecated":null,"id":"inactive","note":null,"stability":null,"value":"inactive"},{"brief":"The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`.\n","deprecated":null,"id":"background","note":null,"stability":null,"value":"background"},{"brief":"The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`.\n","deprecated":null,"id":"foreground","note":null,"stability":null,"value":"foreground"},{"brief":"The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`.\n","deprecated":null,"id":"terminate","note":null,"stability":null,"value":"terminate"}],"name":"ios.state","note":"The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived.\n","requirement_level":{"conditionally_required":"if and only if `os.name` is `ios`"},"stability":"experimental","type":"enum {active, inactive, background, foreground, terminate}"},{"brief":"This attribute represents the state the application has transitioned into at the occurrence of the event.\n","members":[{"brief":"Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time.\n","deprecated":null,"id":"created","note":null,"stability":null,"value":"created"},{"brief":"Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state.\n","deprecated":null,"id":"background","note":null,"stability":null,"value":"background"},{"brief":"Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states.","deprecated":null,"id":"foreground","note":null,"stability":null,"value":"foreground"}],"name":"android.state","note":"The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived.\n","requirement_level":{"conditionally_required":"if and only if `os.name` is `android`"},"stability":"experimental","type":"enum {created, background, foreground}"}],"name":"device.app.lifecycle.fields","requirement_level":"required","type":"map"},"brief":"This event represents an occurrence of a lifecycle transition on Android or iOS platform.\n","event_namespace":"device.app","events":[],"id":"device.app.lifecycle","instrument":null,"lineage":{"source_file":"data/mobile-events.yaml"},"metric_name":null,"name":"device.app.lifecycle","note":"This event identifies the fields that are common to all lifecycle events for android and iOS using the `android.state` and `ios.state` fields. The `android.state` and `ios.state` attributes are mutually exclusive.\n","span_kind":null,"stability":"experimental","type":"event","unit":null},{"attributes":[{"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.\n","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)","name":"exception.stacktrace","requirement_level":"recommended","stability":"stable","type":"string"},{"brief":"SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span.\n","name":"exception.escaped","note":"An exception is considered to have escaped (or left) the scope of a span,\nif that span is ended while the exception is still logically \"in flight\".\nThis may be actually \"in flight\" in some languages (e.g. if the exception\nis passed to a Context manager\u0027s `__exit__` method in Python) but will\nusually be caught at the point of recording the exception in most languages.\n\nIt is usually not possible to determine at the point where an exception is thrown\nwhether it will escape the scope of a span.\nHowever, it is trivial to know that an exception\nwill escape, if one checks for an active exception just before ending the span,\nas done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception).\n\nIt follows that an exception may still escape the scope of the span\neven if the `exception.escaped` attribute was not set or set to false,\nsince the event might have been recorded at a time where it was not\nclear whether the exception will escape.","requirement_level":"recommended","stability":"stable","type":"boolean"},{"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.\n","examples":["java.net.ConnectException","OSError"],"name":"exception.type","requirement_level":{"conditionally_required":"Required if `exception.message` is not set, recommended otherwise."},"stability":"stable","type":"string"},{"brief":"The exception message.","examples":["Division by zero","Can\u0027t convert \u0027int\u0027 object to str implicitly"],"name":"exception.message","requirement_level":{"conditionally_required":"Required if `exception.type` is not set, recommended otherwise."},"stability":"stable","type":"string"}],"brief":"This document defines the attributes used to report a single exception associated with a span.\n","event_namespace":"other","events":[],"id":"trace-exception","instrument":null,"lineage":{"attributes":{"exception.escaped":{"inherited_fields":["brief","note","requirement_level","stability"],"source_group":"registry.exception"},"exception.message":{"inherited_fields":["brief","examples","note","stability"],"locally_overridden_fields":["requirement_level"],"source_group":"registry.exception"},"exception.stacktrace":{"inherited_fields":["brief","examples","note","requirement_level","stability"],"source_group":"registry.exception"},"exception.type":{"inherited_fields":["brief","examples","note","stability"],"locally_overridden_fields":["requirement_level"],"source_group":"registry.exception"}},"source_file":"data/trace-exception.yaml"},"metric_name":null,"name":null,"prefix":"exception","span_kind":null,"type":"event","unit":null}] \ No newline at end of file diff --git a/crates/weaver_resolved_schema/src/any_value.rs b/crates/weaver_resolved_schema/src/any_value.rs index e3add63c..e995577e 100644 --- a/crates/weaver_resolved_schema/src/any_value.rs +++ b/crates/weaver_resolved_schema/src/any_value.rs @@ -18,10 +18,6 @@ pub struct AnyValue { /// Either a string literal denoting the type as a primitive or an /// array type, a template type or an enum definition. pub r#type: String, - /// A description of the type of the AnyValue - /// e.g. "string", "string[]", "int", "enum", "map{ int, string }" - #[serde(skip_serializing_if = "Option::is_none")] - pub type_display: Option, /// A brief description of the AnyValue. #[serde(skip_serializing_if = "String::is_empty")] #[serde(default)] @@ -51,20 +47,10 @@ pub struct AnyValue { /// error. #[serde(skip_serializing_if = "Option::is_none")] pub stability: Option, - /// Specifies if the value is deprecated. The string - /// provided as MUST specify why it's deprecated and/or what - /// to use instead. See also stability. - #[serde(skip_serializing_if = "Option::is_none")] - pub deprecated: Option, /// Identifies the definition of the "fields" of the value when the type is "map". #[serde(skip_serializing_if = "Option::is_none")] pub fields: Option>, /// Used when the type is "enum". - /// Set to false to not accept values other than the specified members. - /// It defaults to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub allow_custom_values: Option, - /// Used when the type is "enum". /// List of enum entries. #[serde(skip_serializing_if = "Option::is_none")] pub members: Option>, diff --git a/crates/weaver_resolver/data/registry-test-4-events/expected-events.json b/crates/weaver_resolver/data/registry-test-4-events/expected-events.json index 2ccc397e..87861a35 100644 --- a/crates/weaver_resolver/data/registry-test-4-events/expected-events.json +++ b/crates/weaver_resolver/data/registry-test-4-events/expected-events.json @@ -16,7 +16,6 @@ "members": [ { "brief": "The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`.\n", - "deprecated": null, "id": "active", "note": null, "stability": null, @@ -24,7 +23,6 @@ }, { "brief": "The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`.\n", - "deprecated": null, "id": "inactive", "note": null, "stability": null, @@ -32,7 +30,6 @@ }, { "brief": "The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`.\n", - "deprecated": null, "id": "background", "note": null, "stability": null, @@ -40,7 +37,6 @@ }, { "brief": "The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`.\n", - "deprecated": null, "id": "foreground", "note": null, "stability": null, @@ -48,7 +44,6 @@ }, { "brief": "The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`.\n", - "deprecated": null, "id": "terminate", "note": null, "stability": null, @@ -70,7 +65,6 @@ "members": [ { "brief": "Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time.\n", - "deprecated": null, "id": "created", "note": null, "stability": null, @@ -78,7 +72,6 @@ }, { "brief": "Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state.\n", - "deprecated": null, "id": "background", "note": null, "stability": null, @@ -86,7 +79,6 @@ }, { "brief": "Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states.", - "deprecated": null, "id": "foreground", "note": null, "stability": null, diff --git a/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json b/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json index c3d8b4fe..3899cb23 100644 --- a/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json +++ b/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json @@ -19,13 +19,11 @@ "body": { "name": "browser.test.event_with_body.fields", "type": "map", - "type_display": "map{ string }", "requirement_level": "required", "fields": [ { "name": "some.field", "type": "string", - "type_display": "string", "brief": "A field that is not referenced in the attributes", "examples": [ "some value", @@ -49,7 +47,6 @@ "body": { "name": "browser.test.event_with_body_details.fields", "type": "map", - "type_display": "map{ string }", "brief": "A map of fields that are not referenced in the attributes", "note": "This map is not referenced in the attributes", "stability": "experimental", @@ -61,7 +58,6 @@ { "name": "some.field", "type": "string", - "type_display": "string", "brief": "A field that is not referenced in the attributes", "examples": [ "some value", @@ -87,13 +83,11 @@ "body": { "name": "client.exception.event.fields", "type": "map", - "type_display": "map{ string, string, string, boolean }", "requirement_level": "optional", "fields": [ { "name": "type", "type": "string", - "type_display": "string", "brief": "The type of the exception.\n", "examples": [ "java.net.ConnectException", @@ -104,7 +98,6 @@ { "name": "message", "type": "string", - "type_display": "string", "brief": "The exception message.", "examples": [ "Division by zero", @@ -115,7 +108,6 @@ { "name": "stacktrace", "type": "string", - "type_display": "string", "brief": "A stacktrace.\n", "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": "optional" @@ -123,7 +115,6 @@ { "name": "escaped", "type": "boolean", - "type_display": "boolean", "brief": "SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span.\n", "requirement_level": "optional", "note": "An exception is considered to have escaped." @@ -212,54 +203,46 @@ "body": { "name": "device.app.lifecycle.fields", "type": "map", - "type_display": "map{ enum {active, inactive, background, foreground, terminate}, enum {created, background, foreground} }", "requirement_level": "required", "fields": [ { "name": "ios.state", - "type": "enum", - "type_display": "enum {active, inactive, background, foreground, terminate}", - "allow_custom_values": false, + "type": "enum {active, inactive, background, foreground, terminate}", "members": [ { "id": "active", "value": "active", "brief": "The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`.\n", "note": null, - "stability": null, - "deprecated": null + "stability": null }, { "id": "inactive", "value": "inactive", "brief": "The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`.\n", "note": null, - "stability": null, - "deprecated": null + "stability": null }, { "id": "background", "value": "background", "brief": "The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`.\n", "note": null, - "stability": null, - "deprecated": null + "stability": null }, { "id": "foreground", "value": "foreground", "brief": "The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`.\n", "note": null, - "stability": null, - "deprecated": null + "stability": null }, { "id": "terminate", "value": "terminate", "brief": "The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`.\n", "note": null, - "stability": null, - "deprecated": null + "stability": null } ], "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", @@ -271,33 +254,28 @@ }, { "name": "android.state", - "type": "enum", - "type_display": "enum {created, background, foreground}", - "allow_custom_values": false, + "type": "enum {created, background, foreground}", "members": [ { "id": "created", "value": "created", "brief": "Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time.\n", "note": null, - "stability": null, - "deprecated": null + "stability": null }, { "id": "background", "value": "background", "brief": "Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state.\n", "note": null, - "stability": null, - "deprecated": null + "stability": null }, { "id": "foreground", "value": "foreground", "brief": "Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states.", "note": null, - "stability": null, - "deprecated": null + "stability": null } ], "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", @@ -472,7 +450,6 @@ "body": { "name": "some.string.body.event.fields", "type": "string", - "type_display": "string", "brief": "This is the body of the event which is a JSON encoded string.\n", "examples": [ "{\"key1\":\"value1\",\"key2\":\"value2\"}" @@ -494,7 +471,6 @@ "body": { "name": "some.string.body.event.fields", "type": "string", - "type_display": "string", "brief": "This is the body of the event which is a JSON encoded string.\n", "note": "This is a detailed note about the body.\n", "stability": "experimental", diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml index f0e0b79e..ae6b3a55 100644 --- a/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml @@ -24,7 +24,6 @@ groups: brief: > This attribute represents the state the application has transitioned into at the occurrence of the event. type: enum - allow_custom_values: false members: - id: active value: 'active' @@ -58,7 +57,6 @@ groups: The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. type: enum - allow_custom_values: false members: - id: created value: 'created' diff --git a/crates/weaver_resolver/src/any_value.rs b/crates/weaver_resolver/src/any_value.rs index a8c97e53..86a751f5 100644 --- a/crates/weaver_resolver/src/any_value.rs +++ b/crates/weaver_resolver/src/any_value.rs @@ -13,19 +13,12 @@ pub fn resolve_any_value_spec(value: &AnyValueSpec) -> AnyValue { let resolved_fields: Vec = fields.iter().map(resolve_any_value_spec).collect(); - construct_any_value_common(value, Some(resolved_fields), None, None) + construct_any_value_common(value, Some(resolved_fields), None) } - AnyValueSpec::Enum { - allow_custom_values, - members, - .. - } => construct_any_value_common( - value, - None, - Some(*allow_custom_values), - Some(members.to_vec()), - ), - _ => construct_any_value_common(value, None, None, None), + AnyValueSpec::Enum { members, .. } => { + construct_any_value_common(value, None, Some(members.to_vec())) + } + _ => construct_any_value_common(value, None, None), } } @@ -33,23 +26,19 @@ pub fn resolve_any_value_spec(value: &AnyValueSpec) -> AnyValue { fn construct_any_value_common( value: &AnyValueSpec, resolved_fields: Option>, - allow_custom_values: Option, members: Option>, ) -> AnyValue { let common = value.common(); AnyValue { name: value.id(), - r#type: value.type_name(), - type_display: Some(value.to_string()), + r#type: value.to_string(), brief: value.brief(), note: value.note(), stability: common.stability.clone(), examples: common.examples.clone(), fields: resolved_fields, requirement_level: common.requirement_level.clone(), - deprecated: common.deprecated.clone(), - allow_custom_values, members, } } diff --git a/crates/weaver_semconv/data/event.yaml b/crates/weaver_semconv/data/event.yaml index d17481a8..244d5fa2 100644 --- a/crates/weaver_semconv/data/event.yaml +++ b/crates/weaver_semconv/data/event.yaml @@ -24,7 +24,6 @@ groups: brief: > This attribute represents the state the application has transitioned into at the occurrence of the event. type: enum - allow_custom_values: false members: - id: active value: 'active' @@ -58,7 +57,6 @@ groups: The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. type: enum - allow_custom_values: false members: - id: created value: 'created' diff --git a/crates/weaver_semconv/data/expected/any_value.json b/crates/weaver_semconv/data/expected/any_value.json index 97479e72..8684091b 100644 --- a/crates/weaver_semconv/data/expected/any_value.json +++ b/crates/weaver_semconv/data/expected/any_value.json @@ -1 +1 @@ -{"body":{"type":"map","id":"id","brief":"brief","note":"note","requirement_level":"optional","fields":[{"type":"enum","id":"id_enum","brief":"brief","note":"note","requirement_level":"optional","allow_custom_values":true,"members":[{"id":"id","value":42,"brief":"brief","note":"note","stability":null,"deprecated":null}]},{"type":"map","id":"id_map","brief":"brief","note":"note","requirement_level":"optional","fields":[{"type":"int","id":"id_int","brief":"brief","note":"note","requirement_level":"required"},{"type":"bytes","id":"id_bytes","brief":"brief","note":"note","requirement_level":"required"},{"type":"string","id":"id_string","brief":"brief","note":"note","requirement_level":"optional"},{"type":"boolean","id":"id_bool","brief":"brief","note":"note","requirement_level":"optional"},{"type":"map","id":"id_nested_map","brief":"brief","note":"note","requirement_level":"optional","fields":[{"type":"int[]","id":"id_nested_int","brief":"brief","note":"note","requirement_level":"optional"},{"type":"double[]","id":"id_nested_bytes","brief":"brief","note":"note","requirement_level":"optional"},{"type":"string[]","id":"id_nested_string","brief":"brief","note":"note","requirement_level":"optional"},{"type":"boolean[]","id":"id_nested_bool","brief":"brief","note":"note","requirement_level":"optional"}]}]},{"type":"int","id":"id_int","brief":"brief","note":"note","requirement_level":"optional"},{"type":"bytes","id":"id_bytes","brief":"brief","note":"note","requirement_level":"optional"},{"type":"string","id":"id_string","brief":"brief","note":"note","requirement_level":"recommended"},{"type":"boolean","id":"id_bool","brief":"brief","note":"note","requirement_level":"optional"},{"type":"double","id":"id_double","brief":"brief","note":"note","requirement_level":"optional"},{"type":"double[]","id":"id_doubles","brief":"brief","note":"note","requirement_level":"optional"}]}} \ No newline at end of file +{"body":{"type":"map","id":"id","brief":"brief","note":"note","requirement_level":"optional","fields":[{"type":"enum","id":"id_enum","brief":"brief","note":"note","requirement_level":"optional","members":[{"id":"id","value":42,"brief":"brief","note":"note","stability":null,"deprecated":null}]},{"type":"map","id":"id_map","brief":"brief","note":"note","requirement_level":"optional","fields":[{"type":"int","id":"id_int","brief":"brief","note":"note","requirement_level":"required"},{"type":"bytes","id":"id_bytes","brief":"brief","note":"note","requirement_level":"required"},{"type":"string","id":"id_string","brief":"brief","note":"note","requirement_level":"optional"},{"type":"boolean","id":"id_bool","brief":"brief","note":"note","requirement_level":"optional"},{"type":"map","id":"id_nested_map","brief":"brief","note":"note","requirement_level":"optional","fields":[{"type":"int[]","id":"id_nested_int","brief":"brief","note":"note","requirement_level":"optional"},{"type":"double[]","id":"id_nested_bytes","brief":"brief","note":"note","requirement_level":"optional"},{"type":"string[]","id":"id_nested_string","brief":"brief","note":"note","requirement_level":"optional"},{"type":"boolean[]","id":"id_nested_bool","brief":"brief","note":"note","requirement_level":"optional"}]}]},{"type":"int","id":"id_int","brief":"brief","note":"note","requirement_level":"optional"},{"type":"bytes","id":"id_bytes","brief":"brief","note":"note","requirement_level":"optional"},{"type":"string","id":"id_string","brief":"brief","note":"note","requirement_level":"recommended"},{"type":"boolean","id":"id_bool","brief":"brief","note":"note","requirement_level":"optional"},{"type":"double","id":"id_double","brief":"brief","note":"note","requirement_level":"optional"},{"type":"double[]","id":"id_doubles","brief":"brief","note":"note","requirement_level":"optional"}]}} \ No newline at end of file diff --git a/crates/weaver_semconv/data/expected/any_value.yaml b/crates/weaver_semconv/data/expected/any_value.yaml index b3555d79..7e3ac6fc 100644 --- a/crates/weaver_semconv/data/expected/any_value.yaml +++ b/crates/weaver_semconv/data/expected/any_value.yaml @@ -10,7 +10,6 @@ body: brief: brief note: note requirement_level: optional - allow_custom_values: true members: - id: id value: 42 diff --git a/crates/weaver_semconv/src/any_value.rs b/crates/weaver_semconv/src/any_value.rs index 90e3d277..9f750265 100644 --- a/crates/weaver_semconv/src/any_value.rs +++ b/crates/weaver_semconv/src/any_value.rs @@ -106,10 +106,6 @@ pub enum AnyValueSpec { #[serde(flatten)] common: AnyValueCommonSpec, - /// Set to false to not accept values other than the specified members. - /// It defaults to true. - #[serde(default = "default_as_true")] - allow_custom_values: bool, /// List of enum entries. members: Vec, }, @@ -150,25 +146,13 @@ pub struct AnyValueCommonSpec { /// "conditionally_required", the string provided as MUST /// specify the conditions under which the field is required. pub requirement_level: RequirementLevel, - /// Specifies if the body field is deprecated. The string - /// provided as MUST specify why it's deprecated and/or what - /// to use instead. See also stability. - #[serde(skip_serializing_if = "Option::is_none")] - pub deprecated: Option, } -/// Implements a human readable display for AnyValueType, used to populate the type_display field. +/// Implements a human readable display for AnyValueType. impl Display for AnyValueSpec { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - AnyValueSpec::Map { fields, .. } => { - let entries = fields - .iter() - .map(|v| format!("{}", v)) - .collect::>() - .join(", "); - write!(f, "map<{}>{{ {} }}", self.id(), entries) - } + AnyValueSpec::Map { .. } => write!(f, "map"), AnyValueSpec::Boolean { .. } => write!(f, "boolean"), AnyValueSpec::Int { .. } => write!(f, "int"), AnyValueSpec::Double { .. } => write!(f, "double"), @@ -185,7 +169,7 @@ impl Display for AnyValueSpec { .map(|m| m.id.clone()) .collect::>() .join(", "); - write!(f, "enum<{}> {{{}}}", self.id(), entries) + write!(f, "enum {{{}}}", entries) } } } @@ -243,31 +227,6 @@ impl AnyValueSpec { let AnyValueCommonSpec { note, .. } = self.common(); note.clone() } - - /// Provides a string representation of the type of the value, with the id for - /// enum and map types. - #[must_use] - pub fn type_name(&self) -> String { - match self { - AnyValueSpec::Map { .. } => "map".to_owned(), - AnyValueSpec::Boolean { .. } => "boolean".to_owned(), - AnyValueSpec::Int { .. } => "int".to_owned(), - AnyValueSpec::Double { .. } => "double".to_owned(), - AnyValueSpec::String { .. } => "string".to_owned(), - AnyValueSpec::Strings { .. } => "string[]".to_owned(), - AnyValueSpec::Ints { .. } => "int[]".to_owned(), - AnyValueSpec::Doubles { .. } => "double[]".to_owned(), - AnyValueSpec::Booleans { .. } => "boolean[]".to_owned(), - AnyValueSpec::Bytes { .. } => "byte[]".to_owned(), - AnyValueSpec::Undefined { .. } => "undefined".to_owned(), - AnyValueSpec::Enum { .. } => "enum".to_owned(), - } - } -} - -/// Specifies the default value for allow_custom_values. -fn default_as_true() -> bool { - true } #[cfg(test)] @@ -279,7 +238,7 @@ mod tests { use super::*; #[test] - fn test_anyvalue_field_type_display() { + fn test_anyvalue_field_format_type() { #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, JsonSchema)] pub struct BodySpec { pub body: AnyValueSpec, @@ -293,7 +252,6 @@ mod tests { stability: None, examples: None, requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), - deprecated: None, }, fields: vec![ AnyValueSpec::Enum { @@ -306,9 +264,7 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, - allow_custom_values: true, members: vec![EnumEntriesSpec { id: "id".to_owned(), value: ValueSpec::Int(42), @@ -328,7 +284,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, fields: vec![ AnyValueSpec::Int { @@ -341,7 +296,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Required, ), - deprecated: None, }, }, AnyValueSpec::Bytes { @@ -354,7 +308,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Required, ), - deprecated: None, }, }, AnyValueSpec::String { @@ -367,7 +320,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Boolean { @@ -380,7 +332,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Map { @@ -393,7 +344,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, fields: vec![ AnyValueSpec::Ints { @@ -406,7 +356,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Doubles { @@ -419,7 +368,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Strings { @@ -432,7 +380,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Booleans { @@ -445,7 +392,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, ], @@ -462,7 +408,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Bytes { @@ -475,7 +420,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::String { @@ -488,7 +432,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Recommended, ), - deprecated: None, }, }, AnyValueSpec::Boolean { @@ -501,7 +444,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Double { @@ -514,7 +456,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, AnyValueSpec::Doubles { @@ -527,7 +468,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional, ), - deprecated: None, }, }, ], @@ -537,6 +477,7 @@ mod tests { let expected_yaml = fs::read_to_string("data/expected/any_value.yaml") .unwrap() .replace("\r\n", "\n"); + assert_eq!( expected_yaml, format!("{}", serde_yaml::to_string(&body).unwrap()), @@ -554,15 +495,7 @@ mod tests { expected_json ); - assert_eq!(format!("{}", map.type_name()), "map",); - - assert_eq!( - format!( - "{}", - map - ), - "map{ enum {id}, map{ int, byte[], string, boolean, map{ int[], double[], string[], boolean[] } }, int, byte[], string, boolean, double, double[] }", - ); + assert_eq!(format!("{}", map), "map",); assert_eq!( format!( @@ -577,7 +510,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -596,7 +528,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -615,7 +546,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -634,7 +564,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -653,7 +582,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -672,7 +600,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -691,7 +618,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -710,7 +636,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -729,7 +654,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -748,7 +672,6 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, } } ), @@ -767,9 +690,7 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, }, - allow_custom_values: true, members: vec![EnumEntriesSpec { id: "id".to_owned(), value: ValueSpec::Int(42), @@ -779,9 +700,8 @@ mod tests { deprecated: None, }] } - .type_name() ), - "enum" + "enum {id}" ); assert_eq!( @@ -797,9 +717,7 @@ mod tests { requirement_level: RequirementLevel::Basic( BasicRequirementLevelSpec::Optional ), - deprecated: None, }, - allow_custom_values: true, members: vec![EnumEntriesSpec { id: "entry1".to_owned(), value: ValueSpec::Int(42), @@ -810,7 +728,7 @@ mod tests { }] } ), - "enum {entry1}" + "enum {entry1}" ); } } diff --git a/crates/weaver_semconv/src/attribute.rs b/crates/weaver_semconv/src/attribute.rs index 324cc8b3..05db77a4 100644 --- a/crates/weaver_semconv/src/attribute.rs +++ b/crates/weaver_semconv/src/attribute.rs @@ -4,6 +4,7 @@ //! Attribute specification. +use crate::any_value::AnyValueSpec; use crate::stability::Stability; use crate::Error; use ordered_float::OrderedFloat; @@ -490,6 +491,73 @@ impl Examples { ), } } + + /// Validation logic for the any_value. + pub(crate) fn validate_any_value( + &self, + any_value: &AnyValueSpec, + group_id: &str, + path_or_url: &str, + ) -> WResult<(), Error> { + match (self, any_value) { + (Examples::Bool(_), AnyValueSpec::Boolean { .. }) + | (Examples::Int(_), AnyValueSpec::Int { .. }) + | (Examples::Double(_), AnyValueSpec::Double { .. }) + | (Examples::String(_), AnyValueSpec::String { .. }) + | (Examples::String(_), AnyValueSpec::Map { .. }) + | (Examples::Ints(_), AnyValueSpec::Int { .. }) + | (Examples::Doubles(_), AnyValueSpec::Double { .. }) + | (Examples::Bools(_), AnyValueSpec::Boolean { .. }) + | (Examples::Strings(_), AnyValueSpec::String { .. }) + | (Examples::Strings(_), AnyValueSpec::Map { .. }) + | (Examples::ListOfInts(_), AnyValueSpec::Ints { .. }) + | (Examples::ListOfDoubles(_), AnyValueSpec::Doubles { .. }) + | (Examples::ListOfBools(_), AnyValueSpec::Booleans { .. }) + | (Examples::ListOfStrings(_), AnyValueSpec::Strings { .. }) + | (Examples::ListOfStrings(_), AnyValueSpec::Map { .. }) => WResult::Ok(()), + (_, AnyValueSpec::Enum { .. }) + | (_, AnyValueSpec::Bytes { .. }) + | (_, AnyValueSpec::Undefined { .. }) => { + // enum, bytes, and undefined types are open so it's not possible to validate the examples + WResult::Ok(()) + } + // Only if future mode is disabled, we allow to have examples following + // the conventions used in semconv 1.27.0 and earlier. + (Examples::Ints(_), AnyValueSpec::Ints { .. }) + | (Examples::Doubles(_), AnyValueSpec::Doubles { .. }) + | (Examples::Bools(_), AnyValueSpec::Booleans { .. }) + | (Examples::Strings(_), AnyValueSpec::Strings { .. }) => WResult::OkWithNFEs( + (), + vec![Error::InvalidAnyValueExampleError { + path_or_url: path_or_url.to_owned(), + group_id: group_id.to_owned(), + value_id: any_value.id(), + error: format!("All examples SHOULD be of type `{}`", any_value), + }], + ), + (_, AnyValueSpec::Map { .. }) => WResult::OkWithNFEs( + (), + vec![Error::InvalidAnyValueExampleError { + path_or_url: path_or_url.to_owned(), + group_id: group_id.to_owned(), + value_id: any_value.id(), + error: format!( + "Examples for `{}` values MUST be a supported string type", + any_value + ), + }], + ), + _ => WResult::OkWithNFEs( + (), + vec![Error::InvalidAnyValueExampleError { + path_or_url: path_or_url.to_owned(), + group_id: group_id.to_owned(), + value_id: any_value.id(), + error: format!("All examples MUST be of type `{}`", any_value), + }], + ), + } + } } /// The different requirement level specifications. diff --git a/crates/weaver_semconv/src/group.rs b/crates/weaver_semconv/src/group.rs index 8f870db6..ffe02fca 100644 --- a/crates/weaver_semconv/src/group.rs +++ b/crates/weaver_semconv/src/group.rs @@ -128,7 +128,8 @@ impl GroupSpec { error: "This group contains an event type with a body definition but the name is not set.".to_owned(), }); } - if self.name.is_none() && self.prefix.is_empty() { + if self.body.is_none() && self.name.is_none() && self.prefix.is_empty() { + // This is ONLY for backward compatibility of span based events. // Must have a name (whether explicit or via a prefix which will derive the name) errors.push(Error::InvalidGroup { path_or_url: path_or_url.to_owned(), @@ -136,6 +137,17 @@ impl GroupSpec { error: "This group contains an event type but the name is not set and no prefix is defined.".to_owned(), }); } + + match validate_any_value_examples( + &mut errors, + self.body.as_ref(), + &self.id, + path_or_url, + ) { + WResult::Ok(_) => {} + WResult::OkWithNFEs(_, errs) => errors.extend(errs), + WResult::FatalErr(err) => return WResult::FatalErr(err), + } } else if self.body.is_some() { // Make sure that body is only used for events errors.push(Error::InvalidGroup { @@ -244,6 +256,62 @@ impl GroupSpec { } } +fn validate_any_value_examples( + errors: &mut Vec, + any_value: Option<&AnyValueSpec>, + group_id: &str, + path_or_url: &str, +) -> WResult<(), Error> { + if let Some(value) = any_value { + let common_examples = &value.common().examples; + if let Some(examples) = common_examples { + match examples.validate_any_value(value, group_id, path_or_url) { + WResult::Ok(_) => {} + WResult::OkWithNFEs(_, errs) => errors.extend(errs), + WResult::FatalErr(err) => return WResult::FatalErr(err), + } + } else { + // No examples are set. + match value { + AnyValueSpec::String { .. } => { + // string values must have examples. + errors.push(Error::InvalidAnyValueExampleError { + path_or_url: path_or_url.to_owned(), + group_id: group_id.to_owned(), + value_id: value.id(), + error: "This value is a string but it does not contain any examples." + .to_owned(), + }); + } + AnyValueSpec::Strings { .. } => { + // string array attributes must have examples. + errors.push(Error::InvalidAnyValueExampleError { + path_or_url: path_or_url.to_owned(), + group_id: group_id.to_owned(), + value_id: value.id(), + error: "This value is a string array but it does not contain any examples." + .to_owned(), + }); + } + _ => {} + } + } + + // Recursively validate the examples for the fields. + if let AnyValueSpec::Map { fields, .. } = value { + for field in fields { + if let WResult::FatalErr(err) = + validate_any_value_examples(errors, Some(field), group_id, path_or_url) + { + return WResult::FatalErr(err); + } + } + } + } + + WResult::Ok(()) +} + /// The different types of groups (specification). #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -336,7 +404,8 @@ impl Display for InstrumentSpec { #[cfg(test)] mod tests { - use crate::attribute::Examples; + use crate::any_value::AnyValueCommonSpec; + use crate::attribute::{BasicRequirementLevelSpec, Examples, RequirementLevel}; use crate::Error::{CompoundError, InvalidExampleWarning, InvalidGroup, InvalidMetric}; use super::*; @@ -534,6 +603,183 @@ mod tests { ); } + #[test] + fn test_validate_event() { + let mut group = GroupSpec { + id: "test".to_owned(), + r#type: GroupType::Event, + name: Some("test_event".to_owned()), + brief: "test".to_owned(), + note: "test".to_owned(), + prefix: "test".to_owned(), + extends: None, + stability: Some(Stability::Deprecated), + deprecated: Some("true".to_owned()), + constraints: vec![], + span_kind: None, + events: vec![], + metric_name: None, + instrument: None, + unit: None, + display_name: None, + attributes: vec![], + body: Some(AnyValueSpec::String { + common: AnyValueCommonSpec { + id: "id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: Some(Examples::String("test".to_owned())), + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + }), + }; + assert!(group + .validate("") + .into_result_failing_non_fatal() + .is_ok()); + + // Examples are mandatory for string attributes. + group.body = Some(AnyValueSpec::String { + common: AnyValueCommonSpec { + id: "string_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: None, + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + }); + + let result = group.validate("").into_result_failing_non_fatal(); + assert_eq!( + Err(Error::InvalidAnyValueExampleError { + path_or_url: "".to_owned(), + group_id: "test".to_owned(), + value_id: "string_id".to_owned(), + error: "This value is a string but it does not contain any examples.".to_owned(), + },), + result + ); + + // Examples are mandatory for strings attributes. + group.body = Some(AnyValueSpec::Strings { + common: AnyValueCommonSpec { + id: "string_array_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: None, + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + }); + let result = group.validate("").into_result_failing_non_fatal(); + assert_eq!( + Err(Error::InvalidAnyValueExampleError { + path_or_url: "".to_owned(), + group_id: "test".to_owned(), + value_id: "string_array_id".to_owned(), + error: "This value is a string array but it does not contain any examples." + .to_owned(), + },), + result + ); + + // Examples are not required for Maps. + group.body = Some(AnyValueSpec::Map { + common: AnyValueCommonSpec { + id: "map_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: None, + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + fields: vec![AnyValueSpec::String { + common: AnyValueCommonSpec { + id: "string_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: Some(Examples::String("test".to_owned())), + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + }], + }); + + assert!(group + .validate("") + .into_result_failing_non_fatal() + .is_ok()); + + // Examples are mandatory for string attributes even if nested + group.body = Some(AnyValueSpec::Map { + common: AnyValueCommonSpec { + id: "map_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: None, + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + fields: vec![AnyValueSpec::String { + common: AnyValueCommonSpec { + id: "nested_string_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: None, + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + }], + }); + + let result = group.validate("").into_result_failing_non_fatal(); + assert_eq!( + Err(Error::InvalidAnyValueExampleError { + path_or_url: "".to_owned(), + group_id: "test".to_owned(), + value_id: "nested_string_id".to_owned(), + error: "This value is a string but it does not contain any examples.".to_owned(), + },), + result + ); + + // Examples are mandatory for strings attributes even if nested + group.body = Some(AnyValueSpec::Map { + common: AnyValueCommonSpec { + id: "map_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: None, + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + fields: vec![AnyValueSpec::Strings { + common: AnyValueCommonSpec { + id: "nested_strings_id".to_owned(), + brief: "brief".to_owned(), + note: "note".to_owned(), + stability: None, + examples: None, + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Optional), + }, + }], + }); + + let result = group.validate("").into_result_failing_non_fatal(); + assert_eq!( + Err(Error::InvalidAnyValueExampleError { + path_or_url: "".to_owned(), + group_id: "test".to_owned(), + value_id: "nested_strings_id".to_owned(), + error: "This value is a string array but it does not contain any examples." + .to_owned(), + },), + result + ); + } + #[test] fn test_instrumentation_spec() { assert_eq!(Counter.to_string(), "counter"); diff --git a/crates/weaver_semconv/src/lib.rs b/crates/weaver_semconv/src/lib.rs index 3f7a298e..e8c8b0ff 100644 --- a/crates/weaver_semconv/src/lib.rs +++ b/crates/weaver_semconv/src/lib.rs @@ -129,6 +129,20 @@ pub enum Error { error: String, }, + /// This indicates that a semantic convention asset contains an invalid example. + #[error("The value `{value_id}` in the group `{group_id}` contains an invalid example. {error}.\nProvenance: {path_or_url:?}")] + #[diagnostic(severity(Error))] + InvalidAnyValueExampleError { + /// The path or URL of the semantic convention asset. + path_or_url: String, + /// The group id of the attribute. + group_id: String, + /// The id of the any_value + value_id: String, + /// The reason of the error. + error: String, + }, + /// A container for multiple errors. #[error("{:?}", format_errors(.0))] CompoundError(#[related] Vec), diff --git a/schemas/semconv-syntax.md b/schemas/semconv-syntax.md index 221f35f2..878dc813 100644 --- a/schemas/semconv-syntax.md +++ b/schemas/semconv-syntax.md @@ -15,12 +15,11 @@ Then, the semantic of each field is described. - [Semantic Convention](#semantic-convention) - [Span semantic convention](#span-semantic-convention) - [Event semantic convention](#event-semantic-convention) - - [Event Body semantic convention](#event-body-semantic-convention) - - [Event Body Field semantic convention](#event-body-field-semantic-convention) - [Event semantic convention example](#event-semantic-convention-example) - [Metric Group semantic convention](#metric-group-semantic-convention) - [Metric semantic convention](#metric-semantic-convention) - [Attribute group semantic convention](#attribute-group-semantic-convention) + - [Any Value semantic convention](#any-value-semantic-convention) - [Attributes](#attributes) - [Examples (for examples)](#examples-for-examples) - [Ref](#ref) @@ -108,12 +107,24 @@ spanfields ::= [events] [span_kind] eventfields ::= name [body] -body ::= body_type [brief] [examples] [stability] [note] [body_fields] +body ::= any_value -body_type ::= "map" +any_value_type ::= "map" | "string" + | "int" + | "double" + | "boolean" + | "string[]" + | "int[]" + | "double[]" + | "boolean[]" + | "byte[]" + | "enum" + | "undefined" -body_fields ::= id type brief [examples] stability [deprecated] [requirement_level] [note] +any_value ::= id any_value_type brief [examples] stability [deprecated] requirement_level [note] [fields] [members] + +fields ::= any_value {any_value} span_kind ::= "client" | "server" @@ -170,45 +181,9 @@ The following is only valid if `type` is `span` (the default): The following is only valid if `type` is `event`: - `name`, required, string. The name of the event. -- `body`, optional, object. Describes the body of the event. - -#### Event Body semantic convention - -The following is only valid if `type` is `event` and `body` is present: - -- `fields`, required, list of fields that describe the event body. - -#### Event Body Field semantic convention - -The following is only valid if `type` is `event` and `body` is present and `fields` is present: +- `body`, optional, [`any value`](#any-value-semantic-convention). Describes the body of the event as an any_value type. -- `id`, required, string. The name of the field. -- `type`, either a string literal denoting the type as a primitive or an array type, a template type or an enum definition (See later). Required. - The accepted string literals are: - * _primitive and array types as string literals:_ - * `"string"`: String attributes. - * `"int"`: Integer attributes. - * `"double"`: Double attributes. - * `"boolean"`: Boolean attributes. - * `"string[]"`: Array of strings attributes. - * `"int[]"`: Array of integer attributes. - * `"double[]"`: Array of double attributes. - * `"boolean[]"`: Array of boolean attributes. - * _template type as string literal:_ `"template[]"` (See [below](#template-type)) - See the [specification of Attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/README.md#attribute) for the definition of the value types. -- `stability`, required, enum. The stability of the field. -- `requirement_level`, optional. Specifies if the field is mandatory. - Can be "required", "conditionally_required", "recommended" or "opt_in". When omitted, the field is "recommended". - When set to "conditionally_required", the string provided as `` MUST specify - the conditions under which the field is required. -- `brief`, `note`, `deprecated`, same meaning as for the whole - [semantic convention](#semantic-convention), but per field. -- `examples`, sequence of example values for the field or single example value. - They are required only for string and string array fields. - Example values must be of the same type of the field. - If only a single example is provided, it can directly be reported without encapsulating it into a sequence/dictionary. See [below](#examples-for-examples). - -#### Event semantic convention example +##### Event semantic convention example ```yaml - id: event.some_event @@ -216,28 +191,58 @@ The following is only valid if `type` is `event` and `body` is present and `fiel type: event brief: "Describes the event." stability: experimental - attributes: # Optional + attributes: # Optional - ref: registry.attribute.id - - ref: registry.some_other.attribute.id # Reference to an existing global attribute - body: # Optional + - ref: registry.some_other.attribute.id # Reference to an existing global attribute + body: # Optional, follows the any_value conventions + id: event_body.some_event.fields type: map - fields: + requirement_level: required + fields: # Unique to this event definition only - id: method type: string stability: experimental brief: "The HTTP method used in the request." examples: ['GET', 'POST'] + requirement_level: required - id: url type: string stability: experimental brief: "The URL of the request." examples: ['http://example.com'] + requirement_level: required - id: status_code type: int stability: experimental brief: "The status code of the response." examples: [200, 404] - ``` + requirement_level: required + - id: nested_map + type: map + stability: experimental + requirement_level: required + fields: + - id: nested_field + type: string # May be any supported any_value type + stability: experimental + requirement_level: required + brief: "A nested field." + examples: ['nested_value'] + - id: nested_enum_state + type: enum + stability: experimental + requirement_level: required + members: + - id: active + value: 'active' + brief: The state became active. + - id: inactive + value: 'inactive' + brief: The state became inactive. + - id: background + value: 'background' + brief: The state is now in the background. +``` #### Metric Group semantic convention @@ -265,6 +270,40 @@ Attribute group (`attribute_group` type) defines a set of attributes that can be declared once and referenced by semantic conventions for different signals, for example spans and logs. Attribute groups don't have any specific fields and follow the general `semconv` semantics. +#### Any Value semantic convention + +Describes the type of the value of an extended (log) attribute or the body of an event. + +- `id`, required, string. The name of the field / any value. +- `type`, either a string literal denoting the type as a primitive or an array type, [an enum definition](#enumeration) or a map of fields. Required. + The accepted string literals are: + * `"string"`: String value. + * `"int"`: Integer value. + * `"double"`: Double value. + * `"boolean"`: Boolean value. + * `"string[]"`: Array of strings value. + * `"int[]"`: Array of integer value. + * `"double[]"`: Array of double value. + * `"boolean[]"`: Array of boolean value. + * `"byte[]"`: Array of bytes value. + * `"map"`: Map of any_value types. + * The `fields` field is required and contains a list of any_value entries that describe each field of the map. + * `"enum"`: Enumerated value. + * The `members` field is required and contains a list of enum entries. + * `"undefined"`: The actually format of the value is not defined. +- `brief`, `note`, `deprecated`, `stability`, same meaning as for the whole + [semantic convention](#semantic-convention), but per field. +- `requirement_level`, required. Specifies if the field is mandatory. + Can be "required", "conditionally_required", "recommended" or "opt_in". When omitted, the field is "recommended". + When set to "conditionally_required", the string provided as `` MUST specify + the conditions under which the field is required. +- `examples`, sequence of example values for the field or single example value. + They are required only for string and string array fields. + Example values must be of the same type of the field or for a map of fields, the type can be of a string type. + If only a single example is provided, it can directly be reported without encapsulating it into a sequence/dictionary. See [below](#examples-for-examples). +- `fields`, required only when the type is `map`, list of any value entries that describe each field of the map. +- `members`, required only when the type is `enum`, list of enum entries. See [below](#enumeration). + ### Attributes An attribute is defined by: @@ -435,3 +474,4 @@ An enum entry has the following fields: - `note`, optional string, longer description. It defaults to an empty string. - `stability`, required stability level. Attributes marked as experimental cannot have stable members. - `deprecated`, optional string, similarly to semantic convention and attribute deprecation, marks specific member as deprecated. + diff --git a/schemas/semconv.schema.json b/schemas/semconv.schema.json index 819ce020..65c7ecd6 100644 --- a/schemas/semconv.schema.json +++ b/schemas/semconv.schema.json @@ -152,7 +152,7 @@ "description": "The name of the event." }, "body": { - "$ref": "#/$defs/BodySemanticConvention" + "$ref": "#/$defs/AnyValueSemanticConvention" } }, "anyOf": [ @@ -163,59 +163,12 @@ } ] }, - "BodySemanticConvention": { + "AnyValueSemanticConvention": { "type": "object", "required": [ - "type" - ], - "properties": { - "type": { - "$ref": "#/$defs/BodyType" - }, - "brief": { - "type": "string", - "description": "a brief description of the field." - }, - "note": { - "type": "string", - "description": "a more elaborate description of the field. It defaults to an empty string." - }, - "stability": { - "allOf": [ - { - "$ref": "#/$defs/StabilityLevel" - } - ] - }, - "examples": { - "anyOf": [ - { - "$ref": "#/$defs/ValueType" - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/ValueType" - } - } - ], - "description": "sequence/dictionary of example values for the field. They are optional for boolean, int, double, and enum attributes. Example values must be of the same type of the field. If only a single example is provided, it can directly be reported without encapsulating it into a sequence/dictionary." - }, - "fields": { - "type": "array", - "items": { - "$ref": "#/$defs/BodyFieldSemanticContention" - } - } - } - }, - "BodyFieldSemanticContention": { - "type": "object", - "required": [ - "id", "type", - "brief", - "stability" + "id", + "requirement_level" ], "properties": { "id": { @@ -223,19 +176,15 @@ "description": "unique string" }, "type": { - "$ref": "#/$defs/AttributeType" + "$ref": "#/$defs/AnyValueType" }, "brief": { "type": "string", - "description": "a brief description of the field." + "description": "a brief description of the value." }, "note": { "type": "string", - "description": "a more elaborate description of the field. It defaults to an empty string." - }, - "deprecated": { - "type": "string", - "description": "specifies if the field is deprecated. The string provided as MUST specify why it's deprecated and/or what to use instead." + "description": "a more elaborate description of the value. It defaults to an empty string." }, "stability": { "allOf": [ @@ -245,7 +194,7 @@ ] }, "requirement_level": { - "description": "specifies the field requirement level. Can be 'required', 'conditionally_required', 'recommended', or 'opt_in'. When omitted, the field is 'recommended'. When set to 'conditionally_required', the string provided MUST specify the conditions under which the field is required.", + "description": "specifies the any value requirement level. Can be 'required', 'conditionally_required', 'recommended', or 'opt_in'. When omitted, the value is 'recommended'. When set to 'conditionally_required', the string provided MUST specify the conditions under which the value is required.", "oneOf": [ { "const": "required" @@ -298,7 +247,63 @@ } } ], - "description": "sequence/dictionary of example values for the field. They are optional for boolean, int, double, and enum attributes. Example values must be of the same type of the field. If only a single example is provided, it can directly be reported without encapsulating it into a sequence/dictionary." + "description": "sequence/dictionary of example values. They are optional for boolean, int, double, and enum attributes. Example values must be of the same type as the value. If only a single example is provided, it can directly be reported without encapsulating it into a sequence/dictionary." + }, + "deprecated": { + "type": "string", + "description": "specifies if the value is deprecated. The string provided as MUST specify why it's deprecated and/or what to use instead." + }, + "fields": { + "type": "array", + "description": "when the type is map, this identifies the child (nested) any values associated with the map.", + "items": { + "$ref": "#/$defs/AnyValueSemanticConvention" + } + }, + "members": { + "type": "array", + "description": "when the type is enum, this identifies the enum members.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "value", + "stability" + ], + "properties": { + "id": { + "type": "string", + "description": "string unique" + }, + "value": { + "type": [ + "string", + "number" + ], + "description": "string or number, value of the enum entry." + }, + "brief": { + "type": "string", + "description": "brief description of the enum entry value. It defaults to the value of ID." + }, + "note": { + "type": "string", + "description": "longer description. It defaults to an empty string." + }, + "deprecated": { + "type": "string", + "description": "specifies if the attribute is deprecated. The string provided as MUST specify why it's deprecated and/or what to use instead." + }, + "stability": { + "allOf": [ + { + "$ref": "#/$defs/StabilityLevel" + } + ] + } + } + } } } }, @@ -589,14 +594,24 @@ } ] }, - "BodyType": { - "description": "specifies the supported body types.", + "AnyValueType": { + "description": "specifies the supported any value types.", "oneOf": [ { "type": "string", "enum": [ "string", - "map" + "int", + "double", + "boolean", + "string[]", + "int[]", + "double[]", + "boolean[]", + "map", + "byte[]", + "enum", + "undefined" ], "description": "literal denoting the type" }