Skip to content

Commit 8085059

Browse files
committed
add NaN serialization/deserialization
1 parent 2abac68 commit 8085059

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

opentelemetry-proto/src/proto.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,64 @@ pub(crate) mod serializers {
200200
.map(|s| s.parse::<u64>().map_err(de::Error::custom))
201201
.collect()
202202
}
203+
204+
205+
// Special serializer and deserializer for NaN, Infinity, and -Infinity
206+
pub fn serialize_f64_special<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
207+
where
208+
S: Serializer,
209+
{
210+
if value.is_nan() {
211+
serializer.serialize_str("NaN")
212+
} else if value.is_infinite() {
213+
if value.is_sign_positive() {
214+
serializer.serialize_str("Infinity")
215+
} else {
216+
serializer.serialize_str("-Infinity")
217+
}
218+
} else {
219+
serializer.serialize_f64(*value)
220+
}
221+
}
222+
223+
pub fn deserialize_f64_special<'de, D>(deserializer: D) -> Result<f64, D::Error>
224+
where
225+
D: Deserializer<'de>,
226+
{
227+
struct F64Visitor;
228+
229+
impl<'de> de::Visitor<'de> for F64Visitor {
230+
type Value = f64;
231+
232+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
233+
formatter.write_str("a float or a string representing NaN, Infinity, or -Infinity")
234+
}
235+
236+
fn visit_f64<E>(self, value: f64) -> Result<f64, E>
237+
where
238+
E: de::Error,
239+
{
240+
Ok(value)
241+
}
242+
243+
fn visit_str<E>(self, value: &str) -> Result<f64, E>
244+
where
245+
E: de::Error,
246+
{
247+
match value {
248+
"NaN" => Ok(f64::NAN),
249+
"Infinity" => Ok(f64::INFINITY),
250+
"-Infinity" => Ok(f64::NEG_INFINITY),
251+
_ => Err(E::custom(format!(
252+
"Invalid string for f64: expected NaN, Infinity, or -Infinity but got '{}'",
253+
value
254+
))),
255+
}
256+
}
257+
}
258+
259+
deserializer.deserialize_any(F64Visitor)
260+
}
203261
}
204262

205263
#[cfg(feature = "gen-tonic-messages")]

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,11 +738,25 @@ pub mod summary_data_point {
738738
/// The quantile of a distribution. Must be in the interval
739739
/// \[0.0, 1.0\].
740740
#[prost(double, tag = "1")]
741+
#[cfg_attr(
742+
feature = "with-serde",
743+
serde(
744+
serialize_with = "crate::proto::serializers::serialize_f64_special",
745+
deserialize_with = "crate::proto::serializers::deserialize_f64_special"
746+
)
747+
)]
741748
pub quantile: f64,
742749
/// The value at the given quantile of a distribution.
743750
///
744751
/// Quantile values must NOT be negative.
745752
#[prost(double, tag = "2")]
753+
#[cfg_attr(
754+
feature = "with-serde",
755+
serde(
756+
serialize_with = "crate::proto::serializers::serialize_f64_special",
757+
deserialize_with = "crate::proto::serializers::deserialize_f64_special"
758+
)
759+
)]
746760
pub value: f64,
747761
}
748762
}

opentelemetry-proto/tests/grpc_build.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,19 @@ fn build_tonic() {
151151
);
152152
}
153153

154+
// Special handling for floating-point fields that might contain NaN, Infinity, or -Infinity
155+
// TODO: More needs to be added here as we find more fields that need this special handling
156+
for path in [
157+
// metrics
158+
"metrics.v1.SummaryDataPoint.ValueAtQuantile.value",
159+
"metrics.v1.SummaryDataPoint.ValueAtQuantile.quantile",
160+
] {
161+
builder = builder.field_attribute(
162+
path,
163+
"#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_f64_special\", deserialize_with = \"crate::proto::serializers::deserialize_f64_special\"))]",
164+
);
165+
}
166+
154167
// special serializer and deserializer for value
155168
// The Value::value field must be hidden
156169
builder = builder

0 commit comments

Comments
 (0)