Skip to content

Commit 306286e

Browse files
authored
fix(proto): use camelCase for json (de)serialization. (#1462)
Part of the effort of #1327 as we need json formats for assertation in integration tests. ## Changes - add configuration to serde to deserialize the json in camelCase field name - add custom (de)serialization for traceId, spanId as they are case-insensitive hex encoded string(see [here](https://opentelemetry.io/docs/specs/otlp/#json-protobuf-encoding)) - add custom (de)serialization for `KeyValue` - add tests for above, and a test using example json files ## Merge requirement checklist * [ x] [CONTRIBUTING](https://github.com/open-telemetry/opentelemetry-rust/blob/main/CONTRIBUTING.md) guidelines followed * [x] Unit tests added/updated (if applicable) * [x] Appropriate `CHANGELOG.md` files updated for non-trivial, user-facing changes * [] Changes in public API reviewed (if applicable)
1 parent 7eb4c27 commit 306286e

13 files changed

+355
-2
lines changed

opentelemetry-proto/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ doctest = false
2323
name = "grpc_build"
2424
path = "tests/grpc_build.rs"
2525

26+
[[test]]
27+
name = "json_deserialize"
28+
path = "tests/json_deserialize.rs"
29+
30+
2631
[features]
2732
default = []
2833

@@ -42,7 +47,7 @@ zpages = ["trace"]
4247

4348
# add ons
4449
with-schemars = ["schemars"]
45-
with-serde = ["serde"]
50+
with-serde = ["serde", "hex"]
4651

4752
[dependencies]
4853
grpcio = { workspace = true, optional = true, features = ["prost-codec"] }
@@ -52,9 +57,11 @@ opentelemetry = { version = "0.21", default-features = false, path = "../opentel
5257
opentelemetry_sdk = { version = "0.21", default-features = false, path = "../opentelemetry-sdk" }
5358
schemars = { version = "0.8", optional = true }
5459
serde = { workspace = true, optional = true, features = ["serde_derive"] }
60+
hex = { version = "0.4.3", optional = true }
5561

5662
[dev-dependencies]
5763
grpcio-compiler = { version = "0.12.1", default-features = false, features = ["prost-codec"] }
5864
tonic-build = { version = "0.9.0" }
5965
prost-build = { version = "0.11.1" }
6066
tempfile = "3.3.0"
67+
serde_json = "1.0"

opentelemetry-proto/src/proto.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,77 @@
1+
/// provide serde support for proto traceIds and spanIds.
2+
/// Those are hex encoded strings in the jsons but they are byte arrays in the proto.
3+
/// See https://opentelemetry.io/docs/specs/otlp/#json-protobuf-encoding for more details
4+
#[cfg(all(feature = "with-serde", feature = "gen-tonic-messages"))]
5+
pub(crate) mod serializers {
6+
use crate::tonic::common::v1::any_value::Value;
7+
use crate::tonic::common::v1::AnyValue;
8+
use serde::de::{self, MapAccess, Visitor};
9+
use serde::ser::SerializeStruct;
10+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
11+
use std::fmt;
12+
13+
// hex string <-> bytes conversion
14+
15+
pub fn serialize_to_hex_string<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
16+
where
17+
S: Serializer,
18+
{
19+
let hex_string = hex::encode(bytes);
20+
serializer.serialize_str(&hex_string)
21+
}
22+
23+
pub fn deserialize_from_hex_string<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
24+
where
25+
D: Deserializer<'de>,
26+
{
27+
struct BytesVisitor;
28+
29+
impl<'de> Visitor<'de> for BytesVisitor {
30+
type Value = Vec<u8>;
31+
32+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
33+
formatter.write_str("a string representing hex-encoded bytes")
34+
}
35+
36+
fn visit_str<E>(self, value: &str) -> Result<Vec<u8>, E>
37+
where
38+
E: de::Error,
39+
{
40+
hex::decode(value).map_err(E::custom)
41+
}
42+
}
43+
44+
deserializer.deserialize_str(BytesVisitor)
45+
}
46+
47+
// AnyValue <-> KeyValue conversion
48+
pub fn serialize_to_value<S>(value: &Option<AnyValue>, serializer: S) -> Result<S::Ok, S::Error>
49+
where
50+
S: Serializer,
51+
{
52+
// Serialize any_value::Value using its own implementation
53+
// If value is None, it will be serialized as such
54+
match value {
55+
Some(value) => value.value.serialize(serializer),
56+
None => serializer.serialize_none(),
57+
}
58+
59+
}
60+
61+
pub fn deserialize_from_value<'de, D>(deserializer: D) -> Result<Option<AnyValue>, D::Error>
62+
where
63+
D: Deserializer<'de>,
64+
{
65+
// Deserialize any_value::Value using its own implementation
66+
let value = Option::<Value>::deserialize(deserializer)?;
67+
68+
// Wrap the deserialized value in AnyValue
69+
Ok(Some(AnyValue { value }))
70+
}
71+
72+
73+
}
74+
175
#[cfg(feature = "gen-tonic-messages")]
276
#[path = "proto/tonic"]
377
/// Generated files using [`tonic`](https://docs.rs/crate/tonic) and [`prost`](https://docs.rs/crate/prost)

opentelemetry-proto/src/proto/tonic/opentelemetry.proto.collector.logs.v1.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
22
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
3+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
34
#[allow(clippy::derive_partial_eq_without_eq)]
45
#[derive(Clone, PartialEq, ::prost::Message)]
56
pub struct ExportLogsServiceRequest {
@@ -15,6 +16,7 @@ pub struct ExportLogsServiceRequest {
1516
}
1617
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
1718
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
19+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
1820
#[allow(clippy::derive_partial_eq_without_eq)]
1921
#[derive(Clone, PartialEq, ::prost::Message)]
2022
pub struct ExportLogsServiceResponse {
@@ -38,6 +40,7 @@ pub struct ExportLogsServiceResponse {
3840
}
3941
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
4042
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
43+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
4144
#[allow(clippy::derive_partial_eq_without_eq)]
4245
#[derive(Clone, PartialEq, ::prost::Message)]
4346
pub struct ExportLogsPartialSuccess {

opentelemetry-proto/src/proto/tonic/opentelemetry.proto.collector.metrics.v1.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
22
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
3+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
34
#[allow(clippy::derive_partial_eq_without_eq)]
45
#[derive(Clone, PartialEq, ::prost::Message)]
56
pub struct ExportMetricsServiceRequest {
@@ -15,6 +16,7 @@ pub struct ExportMetricsServiceRequest {
1516
}
1617
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
1718
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
19+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
1820
#[allow(clippy::derive_partial_eq_without_eq)]
1921
#[derive(Clone, PartialEq, ::prost::Message)]
2022
pub struct ExportMetricsServiceResponse {
@@ -38,6 +40,7 @@ pub struct ExportMetricsServiceResponse {
3840
}
3941
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
4042
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
43+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
4144
#[allow(clippy::derive_partial_eq_without_eq)]
4245
#[derive(Clone, PartialEq, ::prost::Message)]
4346
pub struct ExportMetricsPartialSuccess {

opentelemetry-proto/src/proto/tonic/opentelemetry.proto.collector.trace.v1.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
22
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
3+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
34
#[allow(clippy::derive_partial_eq_without_eq)]
45
#[derive(Clone, PartialEq, ::prost::Message)]
56
pub struct ExportTraceServiceRequest {
@@ -15,6 +16,7 @@ pub struct ExportTraceServiceRequest {
1516
}
1617
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
1718
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
19+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
1820
#[allow(clippy::derive_partial_eq_without_eq)]
1921
#[derive(Clone, PartialEq, ::prost::Message)]
2022
pub struct ExportTraceServiceResponse {
@@ -38,6 +40,7 @@ pub struct ExportTraceServiceResponse {
3840
}
3941
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
4042
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
43+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
4144
#[allow(clippy::derive_partial_eq_without_eq)]
4245
#[derive(Clone, PartialEq, ::prost::Message)]
4346
pub struct ExportTracePartialSuccess {

opentelemetry-proto/src/proto/tonic/opentelemetry.proto.common.v1.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/// object containing arrays, key-value lists and primitives.
44
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
55
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
6+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
67
#[allow(clippy::derive_partial_eq_without_eq)]
78
#[derive(Clone, PartialEq, ::prost::Message)]
89
pub struct AnyValue {
@@ -17,6 +18,7 @@ pub mod any_value {
1718
/// in which case this AnyValue is considered to be "empty".
1819
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
1920
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
21+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
2022
#[allow(clippy::derive_partial_eq_without_eq)]
2123
#[derive(Clone, PartialEq, ::prost::Oneof)]
2224
pub enum Value {
@@ -40,6 +42,7 @@ pub mod any_value {
4042
/// since oneof in AnyValue does not allow repeated fields.
4143
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
4244
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
45+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
4346
#[allow(clippy::derive_partial_eq_without_eq)]
4447
#[derive(Clone, PartialEq, ::prost::Message)]
4548
pub struct ArrayValue {
@@ -54,6 +57,7 @@ pub struct ArrayValue {
5457
/// are semantically equivalent.
5558
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
5659
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
60+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
5761
#[allow(clippy::derive_partial_eq_without_eq)]
5862
#[derive(Clone, PartialEq, ::prost::Message)]
5963
pub struct KeyValueList {
@@ -68,18 +72,28 @@ pub struct KeyValueList {
6872
/// attributes, etc.
6973
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
7074
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
75+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
7176
#[allow(clippy::derive_partial_eq_without_eq)]
7277
#[derive(Clone, PartialEq, ::prost::Message)]
7378
pub struct KeyValue {
7479
#[prost(string, tag = "1")]
7580
pub key: ::prost::alloc::string::String,
7681
#[prost(message, optional, tag = "2")]
82+
#[cfg_attr(
83+
feature = "with-serde",
84+
serde(
85+
serialize_with = "crate::proto::serializers::serialize_to_value",
86+
deserialize_with = "crate::proto::serializers::deserialize_from_value"
87+
)
88+
)]
7789
pub value: ::core::option::Option<AnyValue>,
7890
}
7991
/// InstrumentationScope is a message representing the instrumentation scope information
8092
/// such as the fully qualified name and version.
8193
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
8294
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
95+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
96+
#[cfg_attr(feature = "with-serde", serde(default))]
8397
#[allow(clippy::derive_partial_eq_without_eq)]
8498
#[derive(Clone, PartialEq, ::prost::Message)]
8599
pub struct InstrumentationScope {

opentelemetry-proto/src/proto/tonic/opentelemetry.proto.logs.v1.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
/// as well.
1111
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
1212
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
13+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
1314
#[allow(clippy::derive_partial_eq_without_eq)]
1415
#[derive(Clone, PartialEq, ::prost::Message)]
1516
pub struct LogsData {
@@ -24,6 +25,7 @@ pub struct LogsData {
2425
/// A collection of ScopeLogs from a Resource.
2526
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
2627
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
28+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
2729
#[allow(clippy::derive_partial_eq_without_eq)]
2830
#[derive(Clone, PartialEq, ::prost::Message)]
2931
pub struct ResourceLogs {
@@ -42,6 +44,7 @@ pub struct ResourceLogs {
4244
/// A collection of Logs produced by a Scope.
4345
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
4446
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
47+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
4548
#[allow(clippy::derive_partial_eq_without_eq)]
4649
#[derive(Clone, PartialEq, ::prost::Message)]
4750
pub struct ScopeLogs {
@@ -61,6 +64,7 @@ pub struct ScopeLogs {
6164
/// <https://github.com/open-telemetry/oteps/blob/main/text/logs/0097-log-data-model.md>
6265
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
6366
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
67+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
6468
#[allow(clippy::derive_partial_eq_without_eq)]
6569
#[derive(Clone, PartialEq, ::prost::Message)]
6670
pub struct LogRecord {
@@ -144,6 +148,7 @@ pub struct LogRecord {
144148
/// Possible values for LogRecord.SeverityNumber.
145149
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
146150
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
151+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
147152
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
148153
#[repr(i32)]
149154
pub enum SeverityNumber {
@@ -248,6 +253,7 @@ impl SeverityNumber {
248253
///
249254
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
250255
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
256+
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
251257
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
252258
#[repr(i32)]
253259
pub enum LogRecordFlags {

0 commit comments

Comments
 (0)