Skip to content

Commit

Permalink
AVRO-4024: [Rust] Sync the possible String default values for floatin…
Browse files Browse the repository at this point in the history
…g numbers with Java and C# (#3073)

* AVRO-4024: [Rust] Improve the error messages when parsing unknown String into Value::Float/Double

Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>

* AVRO-4024: [Rust] Accept only "Nan", "INF", "-INF", "Infinity" and "-Infinity"

This is what the Java SDK (via Jackson library) supports (#3066).
This is what the C# SDK also would support (#3070)

Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>

---------

Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
(cherry picked from commit dffc13a)
  • Loading branch information
martin-g committed Aug 7, 2024
1 parent adf5df5 commit 5277c14
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 17 deletions.
4 changes: 2 additions & 2 deletions lang/rust/avro/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ pub enum Error {
GetLong(ValueKind),

#[error("Double expected, got {0:?}")]
GetDouble(ValueKind),
GetDouble(Value),

#[error("Float expected, got {0:?}")]
GetFloat(ValueKind),
GetFloat(Value),

#[error("Bytes expected, got {0:?}")]
GetBytes(ValueKind),
Expand Down
60 changes: 48 additions & 12 deletions lang/rust/avro/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,11 +908,11 @@ 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) {
Value::String(ref x) => match Self::parse_special_float(x) {
Some(f) => Ok(Value::Float(f)),
None => Err(Error::GetFloat(ValueKind::String)),
None => Err(Error::GetFloat(self)),
},
other => Err(Error::GetFloat(other.into())),
other => Err(Error::GetFloat(other)),
}
}

Expand All @@ -922,21 +922,21 @@ 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)),
Value::String(ref x) => match Self::parse_special_float(x) {
Some(f) => Ok(Value::Double(f64::from(f))),
None => Err(Error::GetDouble(self)),
},
other => Err(Error::GetDouble(other.into())),
other => Err(Error::GetDouble(other)),
}
}

/// 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),
"inf" | "+inf" | "infinity" | "+infinity" => Some(f32::INFINITY),
"-inf" | "-infinity" => Some(f32::NEG_INFINITY),
fn parse_special_float(value: &str) -> Option<f32> {
match value {
"NaN" => Some(f32::NAN),
"INF" | "Infinity" => Some(f32::INFINITY),
"-INF" | "-Infinity" => Some(f32::NEG_INFINITY),
_ => None,
}
}
Expand Down Expand Up @@ -3138,4 +3138,40 @@ Field with name '"b"' is not a member of the map items"#,
)
);
}

#[test]
fn avro_4024_resolve_double_from_unknown_string_err() -> TestResult {
let schema = Schema::parse_str(r#"{"type": "double"}"#)?;
let value = Value::String("unknown".to_owned());
match value.resolve(&schema) {
Err(err @ Error::GetDouble(_)) => {
assert_eq!(
format!("{err:?}"),
r#"Double expected, got String("unknown")"#
);
}
other => {
panic!("Expected Error::GetDouble, got {other:?}");
}
}
Ok(())
}

#[test]
fn avro_4024_resolve_float_from_unknown_string_err() -> TestResult {
let schema = Schema::parse_str(r#"{"type": "float"}"#)?;
let value = Value::String("unknown".to_owned());
match value.resolve(&schema) {
Err(err @ Error::GetFloat(_)) => {
assert_eq!(
format!("{err:?}"),
r#"Float expected, got String("unknown")"#
);
}
other => {
panic!("Expected Error::GetFloat, got {other:?}");
}
}
Ok(())
}
}
14 changes: 11 additions & 3 deletions lang/rust/avro/tests/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,22 @@ 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#""float""#, r#""INF""#, Value::Float(f32::INFINITY)),
(r#""double""#, r#""INF""#, Value::Double(f64::INFINITY)),
(r#""float""#, r#""Infinity""#, Value::Float(f32::INFINITY)),
(
r#""float""#,
r#""-Infinity""#,
Value::Float(f32::NEG_INFINITY),
),
(r#""double""#, r#""Infinity""#, Value::Double(f64::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#""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

0 comments on commit 5277c14

Please sign in to comment.