From 61b5c46b91b647f92874c6b736a7e85cec10a34d Mon Sep 17 00:00:00 2001 From: Adam Petro Date: Thu, 22 May 2025 15:43:56 -0400 Subject: [PATCH 1/2] WIP --- api/examples/echo.rs | 5 +++- api/src/lib.rs | 14 ++++++++++++ api/src/read.rs | 54 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/api/examples/echo.rs b/api/examples/echo.rs index 9a40341..0be906a 100644 --- a/api/examples/echo.rs +++ b/api/examples/echo.rs @@ -76,7 +76,10 @@ impl Deserialize for Value { } Ok(Value::Array(arr)) } else { - Err(ReadError::InvalidType) + Err(ReadError::InvalidType { + expected: "value", + actual: value.type_name(), + }) } } } diff --git a/api/src/lib.rs b/api/src/lib.rs index 288689d..0de8d50 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -480,6 +480,20 @@ impl Value { _ => None, } } + + /// Get the type name of the value. + pub fn type_name(&self) -> &'static str { + match self.nan_box.try_decode() { + Ok(ValueRef::Bool(_)) => "boolean", + Ok(ValueRef::Number(_)) => "number", + Ok(ValueRef::String { .. }) => "string", + Ok(ValueRef::Null) => "null", + Ok(ValueRef::Object { .. }) => "object", + Ok(ValueRef::Array { .. }) => "array", + Ok(ValueRef::Error(_)) => "error_code", + Err(_) => "unknown", + } + } } /// A context for reading and writing values. diff --git a/api/src/read.rs b/api/src/read.rs index db22ee4..54bbc95 100644 --- a/api/src/read.rs +++ b/api/src/read.rs @@ -10,8 +10,16 @@ use std::collections::HashMap; #[non_exhaustive] pub enum Error { /// The value is not of the expected type. - #[error("Invalid type")] - InvalidType, + #[error("Invalid type; expected {expected}, got {actual}")] + InvalidType { + /// The expected type. + expected: &'static str, + /// The actual type. + actual: &'static str, + }, + /// A custom error. + #[error(transparent)] + Custom(#[from] Box), } /// A trait for types that can be deserialized from a [`Value`]. @@ -56,14 +64,20 @@ impl Deserialize for () { if value.is_null() { Ok(()) } else { - Err(Error::InvalidType) + Err(Error::InvalidType { + expected: "null", + actual: value.type_name(), + }) } } } impl Deserialize for bool { fn deserialize(value: &Value) -> Result { - value.as_bool().ok_or(Error::InvalidType) + value.as_bool().ok_or_else(|| Error::InvalidType { + expected: "boolean", + actual: value.type_name(), + }) } } @@ -80,7 +94,10 @@ macro_rules! impl_deserialize_for_int { None } }) - .ok_or(Error::InvalidType) + .ok_or_else(|| Error::InvalidType { + expected: stringify!($ty), + actual: value.type_name(), + }) } } }; @@ -99,13 +116,19 @@ impl_deserialize_for_int!(isize); impl Deserialize for f64 { fn deserialize(value: &Value) -> Result { - value.as_number().ok_or(Error::InvalidType) + value.as_number().ok_or_else(|| Error::InvalidType { + expected: "number", + actual: value.type_name(), + }) } } impl Deserialize for String { fn deserialize(value: &Value) -> Result { - value.as_string().ok_or(Error::InvalidType) + value.as_string().ok_or_else(|| Error::InvalidType { + expected: "string", + actual: value.type_name(), + }) } } @@ -128,7 +151,10 @@ impl Deserialize for Vec { } Ok(vec) } else { - Err(Error::InvalidType) + Err(Error::InvalidType { + expected: "array", + actual: value.type_name(), + }) } } } @@ -136,13 +162,21 @@ impl Deserialize for Vec { impl Deserialize for HashMap { fn deserialize(value: &Value) -> Result { let Some(obj_len) = value.obj_len() else { - return Err(Error::InvalidType); + return Err(Error::InvalidType { + expected: "object", + actual: value.type_name(), + }); }; let mut map = HashMap::new(); for i in 0..obj_len { - let key = value.get_obj_key_at_index(i).ok_or(Error::InvalidType)?; + let key = value + .get_obj_key_at_index(i) + .ok_or_else(|| Error::InvalidType { + expected: "string", + actual: value.type_name(), + })?; let value = value.get_at_index(i); map.insert(key, T::deserialize(&value)?); } From c3a9e286a58f3e9ae996e6084e531842192f761e Mon Sep 17 00:00:00 2001 From: Adam Petro Date: Fri, 23 May 2025 15:56:41 -0400 Subject: [PATCH 2/2] More WIP --- api/examples/echo.rs | 2 +- api/src/lib.rs | 17 ++++++- api/src/read.rs | 118 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 123 insertions(+), 14 deletions(-) diff --git a/api/examples/echo.rs b/api/examples/echo.rs index 0be906a..a2dd677 100644 --- a/api/examples/echo.rs +++ b/api/examples/echo.rs @@ -78,7 +78,7 @@ impl Deserialize for Value { } else { Err(ReadError::InvalidType { expected: "value", - actual: value.type_name(), + value: *value, }) } } diff --git a/api/src/lib.rs b/api/src/lib.rs index 0de8d50..23432ff 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -288,7 +288,7 @@ impl CachedInternedStringId { /// - object /// - array /// - error -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Value { context: NonNull, nan_box: NanBox, @@ -496,6 +496,21 @@ impl Value { } } +impl std::fmt::Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.nan_box.try_decode() { + Ok(ValueRef::Bool(b)) => write!(f, "{}", b), + Ok(ValueRef::Number(n)) => write!(f, "{}", n), + Ok(ValueRef::String { .. }) => write!(f, "string"), + Ok(ValueRef::Null) => write!(f, "null"), + Ok(ValueRef::Object { .. }) => write!(f, "object"), + Ok(ValueRef::Array { .. }) => write!(f, "array"), + Ok(ValueRef::Error(e)) => write!(f, "error_code({:?})", e), + Err(_) => write!(f, "unknown"), + } + } +} + /// A context for reading and writing values. /// /// This is created by calling [`Context::new`], and is used to read values from the input and write values to the output. diff --git a/api/src/read.rs b/api/src/read.rs index 54bbc95..81a5ef3 100644 --- a/api/src/read.rs +++ b/api/src/read.rs @@ -10,12 +10,12 @@ use std::collections::HashMap; #[non_exhaustive] pub enum Error { /// The value is not of the expected type. - #[error("Invalid type; expected {expected}, got {actual}")] + #[error("Invalid type; expected {expected}, got {value}")] InvalidType { /// The expected type. expected: &'static str, - /// The actual type. - actual: &'static str, + /// The actual value. + value: Value, }, /// A custom error. #[error(transparent)] @@ -66,7 +66,7 @@ impl Deserialize for () { } else { Err(Error::InvalidType { expected: "null", - actual: value.type_name(), + value: *value, }) } } @@ -76,7 +76,7 @@ impl Deserialize for bool { fn deserialize(value: &Value) -> Result { value.as_bool().ok_or_else(|| Error::InvalidType { expected: "boolean", - actual: value.type_name(), + value: *value, }) } } @@ -96,7 +96,7 @@ macro_rules! impl_deserialize_for_int { }) .ok_or_else(|| Error::InvalidType { expected: stringify!($ty), - actual: value.type_name(), + value: *value, }) } } @@ -118,7 +118,7 @@ impl Deserialize for f64 { fn deserialize(value: &Value) -> Result { value.as_number().ok_or_else(|| Error::InvalidType { expected: "number", - actual: value.type_name(), + value: *value, }) } } @@ -127,7 +127,7 @@ impl Deserialize for String { fn deserialize(value: &Value) -> Result { value.as_string().ok_or_else(|| Error::InvalidType { expected: "string", - actual: value.type_name(), + value: *value, }) } } @@ -137,7 +137,13 @@ impl Deserialize for Option { if value.is_null() { Ok(None) } else { - Ok(Some(T::deserialize(value)?)) + T::deserialize(value).map(Some).map_err(|e| match e { + Error::InvalidType { value, .. } => Error::InvalidType { + expected: std::any::type_name::>(), + value, + }, + e => e, + }) } } } @@ -153,7 +159,7 @@ impl Deserialize for Vec { } else { Err(Error::InvalidType { expected: "array", - actual: value.type_name(), + value: *value, }) } } @@ -164,7 +170,7 @@ impl Deserialize for HashMap { let Some(obj_len) = value.obj_len() else { return Err(Error::InvalidType { expected: "object", - actual: value.type_name(), + value: *value, }); }; @@ -175,7 +181,7 @@ impl Deserialize for HashMap { .get_obj_key_at_index(i) .ok_or_else(|| Error::InvalidType { expected: "string", - actual: value.type_name(), + value: *value, })?; let value = value.get_at_index(i); map.insert(key, T::deserialize(&value)?); @@ -205,6 +211,17 @@ mod tests { }); } + #[test] + fn test_deserialize_bool_error() { + let value = serde_json::json!(1); + let result = deserialize_json_value::(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid type; expected boolean, got 1" + ); + } + macro_rules! test_deserialize_int { ($ty:ty) => { paste::paste! { @@ -216,6 +233,17 @@ mod tests { assert_eq!(result, n); }); } + + #[test] + fn []() { + let value = serde_json::json!(null); + let result = deserialize_json_value::<$ty>(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + concat!("Invalid type; expected ", stringify!($ty), ", got null") + ); + } } }; } @@ -238,6 +266,17 @@ mod tests { assert_eq!(result, 1.0); } + #[test] + fn test_deserialize_f64_error() { + let value = serde_json::json!(null); + let result = deserialize_json_value::(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid type; expected number, got null" + ); + } + #[test] fn test_deserialize_string() { let value = serde_json::json!("test"); @@ -245,6 +284,17 @@ mod tests { assert_eq!(result, "test"); } + #[test] + fn test_deserialize_string_error() { + let value = serde_json::json!(null); + let result = deserialize_json_value::(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid type; expected string, got null" + ); + } + #[test] fn test_deserialize_option() { [None, Some(1), Some(2)].iter().for_each(|&opt| { @@ -254,6 +304,17 @@ mod tests { }); } + #[test] + fn test_deserialize_option_error() { + let value = serde_json::json!("test"); + let result = deserialize_json_value::>(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid type; expected core::option::Option, got string" + ); + } + #[test] fn test_deserialize_vec() { let value = serde_json::json!([1, 2, 3]); @@ -261,6 +322,17 @@ mod tests { assert_eq!(result, vec![1, 2, 3]); } + #[test] + fn test_deserialize_vec_error() { + let value = serde_json::json!("test"); + let result = deserialize_json_value::>(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid type; expected array, got string" + ); + } + #[test] fn test_deserialize_hash_map() { let value = serde_json::json!({ @@ -275,9 +347,31 @@ mod tests { assert_eq!(result, expected); } + #[test] + fn test_deserialize_hash_map_error() { + let value = serde_json::json!("test"); + let result = deserialize_json_value::>(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid type; expected object, got string" + ); + } + #[test] fn test_deserialize_unit() { let value = serde_json::json!(null); deserialize_json_value::<()>(value).unwrap(); } + + #[test] + fn test_deserialize_unit_error() { + let value = serde_json::json!(1); + let result = deserialize_json_value::<()>(value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid type; expected null, got 1" + ); + } }