diff --git a/packages/hurl/Cargo.toml b/packages/hurl/Cargo.toml index cc052c01c5d..4ef3f2e6f25 100644 --- a/packages/hurl/Cargo.toml +++ b/packages/hurl/Cargo.toml @@ -36,7 +36,7 @@ md5 = "0.7.0" percent-encoding = "2.3.1" regex = "1.10.2" serde = "1.0.193" -serde_json = "1.0.108" +serde_json = { version= "1.0.108",features = ["arbitrary_precision"] } sha2 = "0.10.8" url = "2.5.0" xml-rs = { version = "0.8.19" } diff --git a/packages/hurl/src/json/value.rs b/packages/hurl/src/json/value.rs index 5bf5c1a60c1..1a0ce7ea58a 100644 --- a/packages/hurl/src/json/value.rs +++ b/packages/hurl/src/json/value.rs @@ -18,6 +18,7 @@ use base64::engine::general_purpose; use base64::Engine; +use std::str::FromStr; use crate::runner::{Number, Value}; @@ -67,6 +68,10 @@ impl Number { Number::Float(f) => { serde_json::Value::Number(serde_json::Number::from_f64(*f).unwrap()) } + Number::String(s) => { + let number = serde_json::Number::from_str(s).unwrap(); + serde_json::Value::Number(number) + } } } } diff --git a/packages/hurl/src/runner/number.rs b/packages/hurl/src/runner/number.rs index 345f610dc21..91a6b7a497d 100644 --- a/packages/hurl/src/runner/number.rs +++ b/packages/hurl/src/runner/number.rs @@ -25,6 +25,7 @@ use std::fmt; pub enum Number { Float(f64), Integer(i64), + String(String), } // You must implement it yourself because of the Float @@ -33,6 +34,7 @@ impl PartialEq for Number { match (self, other) { (Number::Float(v1), Number::Float(v2)) => (v1 - v2).abs() < f64::EPSILON, (Number::Integer(v1), Number::Integer(v2)) => v1 == v2, + (Number::String(v1), Number::String(v2)) => v1 == v2, _ => false, } } @@ -45,6 +47,7 @@ impl fmt::Display for Number { let value = match self { Number::Float(f) => format_float(*f), Number::Integer(x) => x.to_string(), + Number::String(s) => s.to_string(), }; write!(f, "{value}") } @@ -63,6 +66,7 @@ impl Number { match self { Number::Float(_) => "float".to_string(), Number::Integer(_) => "integer".to_string(), + Number::String(_) => "string".to_string(), } } } @@ -86,6 +90,7 @@ impl Number { (Number::Float(f1), Number::Float(f2)) => compare_float(*f1, *f2), (Number::Integer(i1), Number::Float(f2)) => compare_float(*i1 as f64, *f2), (Number::Float(f1), Number::Integer(i2)) => compare_float(*f1, *i2 as f64), + (n1, n2) => compare_number_string(&n1.to_string(), &n2.to_string()), } } } @@ -100,6 +105,40 @@ fn compare_float(f1: f64, f2: f64) -> Ordering { } } +fn compare_number_string(n1: &str, n2: &str) -> Ordering { + let (neg1, i1, d1) = number_components(n1); + let (neg2, i2, d2) = number_components(n2); + if neg1 == neg2 { + match i1.cmp(i2) { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => d1.cmp(d2), + } + } else if neg1 { + Ordering::Less + } else { + Ordering::Greater + } +} + +// return triple (negative, integer, decimals) +fn number_components(s: &str) -> (bool, &str, &str) { + match s.strip_prefix('-') { + None => match s.find('.') { + None => (false, s.trim_start_matches('0'), ""), + Some(index) => ( + false, + &s[..index].trim_start_matches('0'), + &s[(index + 1)..].trim_end_matches('0'), + ), + }, + Some(s) => { + let (_, integer, decimal) = number_components(s); + (true, integer, decimal) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -110,11 +149,17 @@ mod tests { assert_eq!(Number::Float(1.1).to_string(), "1.1".to_string()); assert_eq!(Number::from(1.0).to_string(), "1.0".to_string()); assert_eq!(Number::from(1.1).to_string(), "1.1".to_string()); + assert_eq!( + Number::String("1.1".to_string()).to_string(), + "1.1".to_string() + ); + assert_eq!(Number::String("1".to_string()).to_string(), "1".to_string()); } #[test] fn test_cmp_value() { let integer_zero = Number::from(0); + let integer_minus_one = Number::from(-1); let integer_one = Number::from(1); let integer_two = Number::from(2); let integer_max = Number::from(i64::max_value()); @@ -128,12 +173,24 @@ mod tests { let float_min = Number::from(f64::MIN); let float_max = Number::from(f64::MAX); + let number_one = Number::String("1".to_string()); + let number_two = Number::String("2".to_string()); + let number_two_with_decimal = Number::String("2.0".to_string()); + + assert_eq!(integer_minus_one.cmp_value(&integer_zero), Ordering::Less); + assert_eq!(integer_one.cmp_value(&integer_one), Ordering::Equal); + assert_eq!(integer_one.cmp_value(&number_one), Ordering::Equal); assert_eq!(integer_one.cmp_value(&float_one), Ordering::Equal); assert_eq!(integer_one.cmp_value(&integer_zero), Ordering::Greater); assert_eq!(integer_one.cmp_value(&float_zero), Ordering::Greater); assert_eq!(integer_one.cmp_value(&integer_two), Ordering::Less); assert_eq!(integer_one.cmp_value(&float_two), Ordering::Less); + assert_eq!(integer_one.cmp_value(&number_two), Ordering::Less); + assert_eq!( + integer_one.cmp_value(&number_two_with_decimal), + Ordering::Less + ); assert_eq!(integer_min.cmp_value(&float_min), Ordering::Greater); assert_eq!(integer_max.cmp_value(&float_max), Ordering::Less); @@ -156,4 +213,27 @@ mod tests { Ordering::Equal ); } + + #[test] + fn test_cmp_number_string() { + assert_eq!(compare_number_string("1", "1"), Ordering::Equal); + assert_eq!(compare_number_string("1", "1.0"), Ordering::Equal); + assert_eq!(compare_number_string("1.000", "1.0"), Ordering::Equal); + assert_eq!(compare_number_string("1", "2"), Ordering::Less); + assert_eq!(compare_number_string("1.1", "2"), Ordering::Less); + assert_eq!(compare_number_string("-001.1000", "-1.1"), Ordering::Equal); + } + + #[test] + fn test_number_components() { + assert_eq!(number_components("1"), (false, "1", "")); + assert_eq!(number_components("1.0"), (false, "1", "")); + assert_eq!(number_components("01"), (false, "1", "")); + + assert_eq!(number_components("1.1"), (false, "1", "1")); + assert_eq!(number_components("1.100"), (false, "1", "1")); + + assert_eq!(number_components("-1.1"), (true, "1", "1")); + assert_eq!(number_components("-01.100"), (true, "1", "1")); + } } diff --git a/packages/hurl/src/runner/predicate.rs b/packages/hurl/src/runner/predicate.rs index da2ecc3287d..abd83ce67cc 100644 --- a/packages/hurl/src/runner/predicate.rs +++ b/packages/hurl/src/runner/predicate.rs @@ -135,6 +135,7 @@ impl Number { match self { Number::Float(f) => format!("float <{}>", format_float(*f)), Number::Integer(v) => format!("int <{v}>"), + Number::String(s) => format!("number <{s}>"), } } } @@ -162,6 +163,7 @@ impl Number { match self { Number::Float(f) => format!("float <{}>", format_float(*f)), Number::Integer(value) => format!("integer <{value}>"), + Number::String(s) => format!("number <{s}>"), } } } diff --git a/packages/hurl/src/runner/query.rs b/packages/hurl/src/runner/query.rs index 001e8519c03..a2a44085032 100644 --- a/packages/hurl/src/runner/query.rs +++ b/packages/hurl/src/runner/query.rs @@ -326,8 +326,10 @@ impl Value { serde_json::Value::Number(n) => { if n.is_f64() { Value::Number(Number::from(n.as_f64().unwrap())) - } else { + } else if n.is_i64() { Value::Number(Number::from(n.as_i64().unwrap())) + } else { + Value::Number(Number::String(n.to_string())) } } serde_json::Value::String(s) => Value::String(s.to_string()), @@ -522,6 +524,28 @@ pub mod tests { } } + #[test] + pub fn value_from_json() { + let json_number: serde_json::Value = serde_json::from_str("1000").unwrap(); + assert_eq!( + Value::from_json(&json_number), + Value::Number(Number::Integer(1000)) + ); + + let json_number: serde_json::Value = serde_json::from_str("1.0").unwrap(); + assert_eq!( + Value::from_json(&json_number), + Value::Number(Number::Float(1.0)) + ); + + let json_number: serde_json::Value = + serde_json::from_str("1000000000000000000000").unwrap(); + assert_eq!( + Value::from_json(&json_number), + Value::Number(Number::String("1000000000000000000000".to_string())) + ) + } + #[test] fn test_query_status() { let variables = HashMap::new();