@@ -58,35 +58,26 @@ fn json_to_cbor_value(value: &serde_json::Value) -> Result<cbor::value::Value, V
5858 serde_json:: Value :: String ( s) => cbor:: value:: Value :: Text ( s. clone ( ) ) ,
5959
6060 serde_json:: Value :: Number ( n) => {
61- if let Some ( i) = n. as_i64 ( ) {
62- if i >= 0 {
63- cbor:: value:: Value :: Positive ( i as u64 )
64- } else {
65- let mag: u64 =
66- ( -1i128 - i as i128 )
67- . try_into ( )
68- . map_err ( |_| ValidationError {
69- details : vec ! [ ErrorDetail {
70- code: "PUBLIC_INFO_NEGATIVE_INTEGER_OUT_OF_RANGE" . to_string( ) ,
71- path: "publicInfo" . to_string( ) ,
72- message: format!( "negative integer out of range: {:?}" , i) ,
73- } ] ,
74- } ) ?;
75- cbor:: value:: Value :: Negative ( mag)
76- }
77- } else if let Some ( u) = n. as_u64 ( ) {
78- cbor:: value:: Value :: Positive ( u)
79- } else if let Some ( f) = n. as_f64 ( ) {
80- cbor:: value:: Value :: Float ( f)
61+ if let Some ( posint) = n. as_u64 ( ) {
62+ // postive number that fits in u64
63+ cbor:: value:: Value :: Positive ( posint)
64+ } else if let Some ( negint) = n. as_i64 ( ) {
65+ // this number is definitely negative
66+ let negintmag: u64 = ( -1i64 - negint) as u64 ;
67+ cbor:: value:: Value :: Negative ( negintmag)
68+ } else if let Some ( float) = n. as_f64 ( ) {
69+ // either a float, or an integer that is too big
70+ // for serialization without precision loss
71+ cbor:: value:: Value :: Float ( float)
8172 } else {
73+ // this should never happen, as serde_json::Number should always be one of the above 3 cases
74+ // it does happen with way too big Float numbers such as `1e400`. In this case, we produce a
75+ // validation error as it did not fit in u64, i64 or f64.
8276 return Err ( ValidationError {
8377 details : vec ! [ ErrorDetail {
8478 code: "PUBLIC_INFO_NUMBER_NOT_REPRESENTABLE" . to_string( ) ,
79+ message: format!( "Unable to parse this json number. {:?}" , n) ,
8580 path: "publicInfo" . to_string( ) ,
86- message: format!(
87- "serde_json::Number not representable as i64/u64/f64: {:?}" ,
88- n
89- ) ,
9081 } ] ,
9182 } ) ;
9283 }
@@ -114,6 +105,7 @@ fn json_to_cbor_value(value: &serde_json::Value) -> Result<cbor::value::Value, V
114105mod tests {
115106 use super :: * ;
116107
108+ /// Positive Test Scenarios
117109 #[ test]
118110 fn test_public_info_none_returns_ok_none ( ) {
119111 let public_info: Option < serde_json:: Value > = None ;
@@ -124,32 +116,6 @@ mod tests {
124116 assert ! ( result. unwrap( ) . is_none( ) ) ;
125117 }
126118
127- #[ test]
128- fn test_public_info_not_json_object_at_root_validation_error ( ) {
129- // The expected validation error
130- let expected_error = ValidationError {
131- details : vec ! [ ErrorDetail {
132- code: "PUBLIC_INFO_EXPECTED_JSON" . to_string( ) ,
133- path: "publicInfo" . to_string( ) ,
134- message: "expected json at top level, got: Number(1)" . to_string( ) ,
135- } ] ,
136- } ;
137-
138- // creating a public info that produces the validation error (just a json number rather than an object at root)
139- let public_info: Option < serde_json:: Value > =
140- Some ( serde_json:: Value :: Number ( serde_json:: Number :: from ( 1u64 ) ) ) ;
141-
142- // call our convert function
143- let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
144-
145- // ensure we have a result in error
146- assert ! ( result. is_err( ) ) ;
147-
148- let result_error = result. unwrap_err ( ) ;
149-
150- assert_eq ! ( result_error, expected_error) ;
151- }
152-
153119 #[ test]
154120 fn test_parse_public_info_success_object ( ) {
155121 let session_id_val = "alpha-numeric-1234-5678-sessionid-example" ;
@@ -179,15 +145,6 @@ mod tests {
179145 ) ;
180146 }
181147
182- #[ test]
183- fn test_parse_public_info_fails_when_not_object ( ) {
184- let public_info = Some ( serde_json:: json!( "hello" ) ) ;
185-
186- let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
187-
188- assert ! ( result. is_err( ) , "top-level non-object should error" ) ;
189- }
190-
191148 #[ test]
192149 fn test_nested_conversion ( ) {
193150 let public_info = Some ( serde_json:: json!( {
@@ -236,4 +193,209 @@ mod tests {
236193 Some ( & cbor:: value:: Value :: Negative ( 211 ) )
237194 ) ;
238195 }
196+
197+ /// Error scenarios and validations.
198+ #[ test]
199+ fn test_parse_public_info_fails_when_not_object ( ) {
200+ let public_info = Some ( serde_json:: json!( "hello" ) ) ;
201+
202+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
203+
204+ assert ! ( result. is_err( ) , "top-level non-object should error" ) ;
205+ }
206+
207+ /// Public Info number parsing tests and constraints
208+ #[ test]
209+ fn test_public_info_positive_integer_parsed_to_cbor_positive ( ) {
210+ let public_info: Option < serde_json:: Value > =
211+ Some ( serde_json:: from_str ( r#"{"simplePositiveInt": 42}"# ) . unwrap ( ) ) ;
212+
213+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
214+ assert ! ( result. is_ok( ) ) ;
215+
216+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
217+ assert_eq ! (
218+ map. get( "simplePositiveInt" ) ,
219+ Some ( & cbor:: value:: Value :: Positive ( 42 ) )
220+ ) ;
221+ }
222+
223+ #[ test]
224+ fn test_public_info_negative_one_parsed_to_cbor_negative_zero ( ) {
225+ let public_info: Option < serde_json:: Value > =
226+ Some ( serde_json:: from_str ( r#"{"simpleNegative": -1}"# ) . unwrap ( ) ) ;
227+
228+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
229+ assert ! ( result. is_ok( ) ) ;
230+
231+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
232+ assert_eq ! (
233+ map. get( "simpleNegative" ) ,
234+ Some ( & cbor:: value:: Value :: Negative ( 0 ) )
235+ ) ;
236+ }
237+
238+ /// Minimum negative that can be stored in CBOR
239+ #[ test]
240+ fn test_public_info_cbor_i64_min_negative ( ) {
241+ let public_info: Option < serde_json:: Value > =
242+ Some ( serde_json:: from_str ( r#"{"i64min": -9223372036854775808}"# ) . unwrap ( ) ) ;
243+
244+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
245+ assert ! ( result. is_ok( ) ) ;
246+
247+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
248+
249+ assert_eq ! (
250+ map. get( "i64min" ) ,
251+ Some ( & cbor:: value:: Value :: Negative ( i64 :: MAX as u64 ) )
252+ ) ;
253+ }
254+
255+ /// Max positive that can be stored in CBOR
256+ #[ test]
257+ fn test_public_info_cbor_max_u64_positive ( ) {
258+ let public_info: Option < serde_json:: Value > =
259+ Some ( serde_json:: from_str ( r#"{"u64max": 18446744073709551615}"# ) . unwrap ( ) ) ;
260+
261+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
262+ assert ! ( result. is_ok( ) ) ;
263+
264+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
265+ assert_eq ! (
266+ map. get( "u64max" ) ,
267+ Some ( & cbor:: value:: Value :: Positive ( u64 :: MAX ) )
268+ ) ;
269+ }
270+
271+ /// Test standard float works as expected.
272+ #[ test]
273+ fn test_public_info_simple_float_parsed_to_cbor_float ( ) {
274+ let public_info: Option < serde_json:: Value > =
275+ Some ( serde_json:: from_str ( r#"{"simpleFloat": 0.1}"# ) . unwrap ( ) ) ;
276+
277+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
278+ assert ! ( result. is_ok( ) ) ;
279+
280+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
281+ assert_eq ! (
282+ map. get( "simpleFloat" ) ,
283+ Some ( & cbor:: value:: Value :: Float ( 0.1 ) )
284+ ) ;
285+ }
286+
287+ /// Ensure float followed by .0 is stored as a Float.
288+ #[ test]
289+ fn test_public_info_float_with_dot_zero_stays_float ( ) {
290+ let public_info: Option < serde_json:: Value > =
291+ Some ( serde_json:: from_str ( r#"{"onePointZero": 1.0}"# ) . unwrap ( ) ) ;
292+
293+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
294+ assert ! ( result. is_ok( ) ) ;
295+
296+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
297+ assert_eq ! (
298+ map. get( "onePointZero" ) ,
299+ Some ( & cbor:: value:: Value :: Float ( 1.0 ) )
300+ ) ;
301+ }
302+
303+ /// Ensure scientific numbers with exponential are stored
304+ /// as their true Float value
305+ #[ test]
306+ fn test_public_info_scientific_notation_parsed_to_cbor_float ( ) {
307+ let public_info: Option < serde_json:: Value > =
308+ Some ( serde_json:: from_str ( r#"{"sci": 1e3}"# ) . unwrap ( ) ) ;
309+
310+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
311+ assert ! ( result. is_ok( ) ) ;
312+
313+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
314+ assert_eq ! ( map. get( "sci" ) , Some ( & cbor:: value:: Value :: Float ( 1000.0 ) ) ) ;
315+ }
316+
317+ /// Test a super large Float, returns with Validation
318+ /// Error as not representable.
319+ #[ test]
320+ fn test_public_info_float_out_of_f64_range_errors ( ) {
321+ let public_info: Option < serde_json:: Value > =
322+ Some ( serde_json:: from_str ( r#"{"tooBigFloat": 1e400}"# ) . unwrap ( ) ) ;
323+
324+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
325+ assert ! ( result. is_err( ) ) ;
326+
327+ let result = result. unwrap_err ( ) ;
328+
329+ assert_eq ! ( result. details. len( ) , 1 ) ;
330+ assert_eq ! (
331+ result. details[ 0 ] . code,
332+ "PUBLIC_INFO_NUMBER_NOT_REPRESENTABLE"
333+ ) ;
334+ assert_eq ! ( result. details[ 0 ] . path, "publicInfo" ) ;
335+ assert ! (
336+ result. details[ 0 ]
337+ . message
338+ . contains( "Unable to parse this json number" )
339+ ) ;
340+ }
341+
342+ // Float with 14 digits after decimal is precise
343+ #[ test]
344+ fn test_public_info_float_14_digits_after_decimal_is_precise ( ) {
345+ let public_info: Option < serde_json:: Value > =
346+ Some ( serde_json:: from_str ( r#"{"preciseFloat": 99.99999999999999}"# ) . unwrap ( ) ) ;
347+
348+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
349+ assert ! ( result. is_ok( ) ) ;
350+
351+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
352+ assert_eq ! (
353+ map. get( "preciseFloat" ) ,
354+ Some ( & cbor:: value:: Value :: Float ( 99.99999999999999 ) )
355+ ) ;
356+ }
357+
358+ // Float with 15 digits after decimal loses precision
359+ #[ test]
360+ fn test_public_info_float_15_digits_after_decimal_loses_precision ( ) {
361+ let public_info: Option < serde_json:: Value > =
362+ Some ( serde_json:: from_str ( r#"{"preciseFloat": 99.999999999999999}"# ) . unwrap ( ) ) ;
363+
364+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
365+ assert ! ( result. is_ok( ) ) ;
366+
367+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
368+
369+ // proof that precision gets lost for this
370+ assert_eq ! (
371+ map. get( "preciseFloat" ) ,
372+ Some ( & cbor:: value:: Value :: Float ( 100.0 ) )
373+ ) ;
374+ }
375+
376+ #[ test]
377+ fn test_public_info_zero_successfully_recorded_as_cbor_positive ( ) {
378+ let public_info: Option < serde_json:: Value > =
379+ Some ( serde_json:: from_str ( r#"{"val": 0}"# ) . unwrap ( ) ) ;
380+
381+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
382+ assert ! ( result. is_ok( ) ) ;
383+
384+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
385+
386+ assert_eq ! ( map. get( "val" ) , Some ( & cbor:: value:: Value :: Positive ( 0 ) ) ) ;
387+ }
388+
389+ #[ test]
390+ fn test_public_info_neg_zero_successfully_recorded_as_cbor_positive ( ) {
391+ let public_info: Option < serde_json:: Value > =
392+ Some ( serde_json:: from_str ( r#"{"val": -0}"# ) . unwrap ( ) ) ;
393+
394+ let result = convert_public_info_to_hashmap_of_string_to_cbor ( & public_info) ;
395+ assert ! ( result. is_ok( ) ) ;
396+
397+ let map = result. unwrap ( ) . expect ( "should have public info" ) ;
398+
399+ assert_eq ! ( map. get( "val" ) , Some ( & cbor:: value:: Value :: Positive ( 0 ) ) ) ;
400+ }
239401}
0 commit comments