From dffc13a6b3b71bfd6fd01e3041955e65c0697f3b Mon Sep 17 00:00:00 2001 From: Martin Grigorov Date: Wed, 7 Aug 2024 14:42:38 +0300 Subject: [PATCH] AVRO-4024: [Rust] Sync the possible String default values for floating 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 * AVRO-4024: [Rust] Accept only "Nan", "INF", "-INF", "Infinity" and "-Infinity" This is what the Java SDK (via Jackson library) supports (https://github.com/apache/avro/pull/3066). This is what the C# SDK also would support (https://github.com/apache/avro/pull/3070) Signed-off-by: Martin Tzvetanov Grigorov --------- Signed-off-by: Martin Tzvetanov Grigorov --- lang/rust/avro/src/error.rs | 4 +-- lang/rust/avro/src/types.rs | 60 +++++++++++++++++++++++++++++-------- lang/rust/avro/tests/io.rs | 14 +++++++-- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/lang/rust/avro/src/error.rs b/lang/rust/avro/src/error.rs index 6e0de0dcb44..c4ec2baa83d 100644 --- a/lang/rust/avro/src/error.rs +++ b/lang/rust/avro/src/error.rs @@ -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), diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs index 0840c87ae9d..0ce6afc535a 100644 --- a/lang/rust/avro/src/types.rs +++ b/lang/rust/avro/src/types.rs @@ -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)), } } @@ -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 { - 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 { + match value { + "NaN" => Some(f32::NAN), + "INF" | "Infinity" => Some(f32::INFINITY), + "-INF" | "-Infinity" => Some(f32::NEG_INFINITY), _ => None, } } @@ -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(()) + } } diff --git a/lang/rust/avro/tests/io.rs b/lang/rust/avro/tests/io.rs index 6a59a507132..c6589a978f1 100644 --- a/lang/rust/avro/tests/io.rs +++ b/lang/rust/avro/tests/io.rs @@ -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""#,