Skip to content

Commit

Permalink
add NaN serialization/deserialization
Browse files Browse the repository at this point in the history
  • Loading branch information
lalitb committed Jan 7, 2025
1 parent 2abac68 commit 8085059
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 0 deletions.
58 changes: 58 additions & 0 deletions opentelemetry-proto/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,64 @@ pub(crate) mod serializers {
.map(|s| s.parse::<u64>().map_err(de::Error::custom))
.collect()
}


// Special serializer and deserializer for NaN, Infinity, and -Infinity
pub fn serialize_f64_special<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if value.is_nan() {
serializer.serialize_str("NaN")
} else if value.is_infinite() {
if value.is_sign_positive() {
serializer.serialize_str("Infinity")
} else {
serializer.serialize_str("-Infinity")
}
} else {
serializer.serialize_f64(*value)
}
}

pub fn deserialize_f64_special<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
struct F64Visitor;

impl<'de> de::Visitor<'de> for F64Visitor {
type Value = f64;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a float or a string representing NaN, Infinity, or -Infinity")
}

fn visit_f64<E>(self, value: f64) -> Result<f64, E>
where
E: de::Error,
{
Ok(value)
}

fn visit_str<E>(self, value: &str) -> Result<f64, E>
where
E: de::Error,
{
match value {
"NaN" => Ok(f64::NAN),
"Infinity" => Ok(f64::INFINITY),
"-Infinity" => Ok(f64::NEG_INFINITY),
_ => Err(E::custom(format!(
"Invalid string for f64: expected NaN, Infinity, or -Infinity but got '{}'",
value
))),
}
}
}

deserializer.deserialize_any(F64Visitor)
}
}

#[cfg(feature = "gen-tonic-messages")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -738,11 +738,25 @@ pub mod summary_data_point {
/// The quantile of a distribution. Must be in the interval
/// \[0.0, 1.0\].
#[prost(double, tag = "1")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_f64_special",
deserialize_with = "crate::proto::serializers::deserialize_f64_special"
)
)]
pub quantile: f64,
/// The value at the given quantile of a distribution.
///
/// Quantile values must NOT be negative.
#[prost(double, tag = "2")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_f64_special",
deserialize_with = "crate::proto::serializers::deserialize_f64_special"
)
)]
pub value: f64,
}
}
Expand Down
13 changes: 13 additions & 0 deletions opentelemetry-proto/tests/grpc_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ fn build_tonic() {
);
}

// Special handling for floating-point fields that might contain NaN, Infinity, or -Infinity
// TODO: More needs to be added here as we find more fields that need this special handling
for path in [
// metrics
"metrics.v1.SummaryDataPoint.ValueAtQuantile.value",
"metrics.v1.SummaryDataPoint.ValueAtQuantile.quantile",
] {
builder = builder.field_attribute(
path,
"#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_f64_special\", deserialize_with = \"crate::proto::serializers::deserialize_f64_special\"))]",
);
}

// special serializer and deserializer for value
// The Value::value field must be hidden
builder = builder
Expand Down

0 comments on commit 8085059

Please sign in to comment.