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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lang/rust/avro/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,10 @@ impl Value {
Value::Long(n) => Ok(Value::Float(n as f32)),
Value::Float(x) => Ok(Value::Float(x)),
Value::Double(x) => Ok(Value::Float(x as f32)),
Value::String(x) => match Self::parse_special_float(&x) {
Some(f) => Ok(Value::Float(f)),
None => Err(Error::GetFloat(ValueKind::String)),
},
other => Err(Error::GetFloat(other.into())),
}
}
Expand All @@ -918,10 +922,25 @@ impl Value {
Value::Long(n) => Ok(Value::Double(n as f64)),
Value::Float(x) => Ok(Value::Double(f64::from(x))),
Value::Double(x) => Ok(Value::Double(x)),
Value::String(x) => match Self::parse_special_float(&x) {
Some(f) => Ok(Value::Double(f.into())),
None => Err(Error::GetDouble(ValueKind::String)),
},
other => Err(Error::GetDouble(other.into())),
}
}

/// IEEE 754 NaN and infinities are not valid JSON numbers.
/// So they are represented in JSON as strings.
fn parse_special_float(s: &str) -> Option<f32> {
match s.trim().to_ascii_lowercase().as_str() {
"nan" | "+nan" | "-nan" => Some(f32::NAN),
Copy link
Member

@martin-g martin-g Aug 6, 2024

Choose a reason for hiding this comment

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

Java and C++ use case-sensitive values.
I suggest to compare against NaN, Inf, Infinity, etc., i.e. let's remove to_ascii_lowercase()

"inf" | "+inf" | "infinity" | "+infinity" => Some(f32::INFINITY),
"-inf" | "-infinity" => Some(f32::NEG_INFINITY),
_ => None,
}
}

fn resolve_bytes(self) -> Result<Self, Error> {
match self {
Value::Bytes(bytes) => Ok(Value::Bytes(bytes)),
Expand Down
48 changes: 43 additions & 5 deletions lang/rust/avro/tests/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ fn default_value_examples() -> &'static Vec<(&'static str, &'static str, Value)>
(r#""long""#, "5", Value::Long(5)),
(r#""float""#, "1.1", Value::Float(1.1)),
(r#""double""#, "1.1", Value::Double(1.1)),
(r#""float""#, r#"" +inf ""#, Value::Float(f32::INFINITY)),
(
r#""double""#,
r#""-Infinity""#,
Value::Double(f64::NEG_INFINITY),
),
(r#""float""#, r#""-NAN""#, Value::Float(f32::NAN)),
(r#""double""#, r#""-NAN""#, Value::Double(f64::NAN)),
(
r#"{"type": "fixed", "name": "F", "size": 2}"#,
r#""a""#,
Expand Down Expand Up @@ -312,11 +320,41 @@ fn test_default_value() -> TestResult {
&mut Cursor::new(encoded),
Some(&reader_schema),
)?;
assert_eq!(
datum_read, datum_to_read,
"{} -> {}",
*field_type, *default_json
);

match default_datum {
// For float/double, NaN != NaN, so we check specially here.
Value::Double(f) if f.is_nan() => {
let Value::Record(fields) = datum_read else {
unreachable!("the test always constructs top level as record")
};
let Value::Double(f) = fields[0].1 else {
panic!("double expected")
};
assert!(
f.is_nan(),
"{field_type} -> {default_json} is parsed as {f} rather than NaN"
);
}
Value::Float(f) if f.is_nan() => {
let Value::Record(fields) = datum_read else {
unreachable!("the test always constructs top level as record")
};
let Value::Float(f) = fields[0].1 else {
panic!("double expected")
};
assert!(
f.is_nan(),
"{field_type} -> {default_json} is parsed as {f} rather than NaN"
);
}
_ => {
assert_eq!(
datum_read, datum_to_read,
"{} -> {}",
*field_type, *default_json
);
}
}
}

Ok(())
Expand Down