diff --git a/api/examples/echo.rs b/api/examples/echo.rs index 9a40341..a2dd677 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", + value: *value, + }) } } } diff --git a/api/src/lib.rs b/api/src/lib.rs index 288689d..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, @@ -480,6 +480,35 @@ 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", + } + } +} + +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. diff --git a/api/src/read.rs b/api/src/read.rs index db22ee4..81a5ef3 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 {value}")] + InvalidType { + /// The expected type. + expected: &'static str, + /// The actual value. + value: Value, + }, + /// 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", + value: *value, + }) } } } 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", + value: *value, + }) } } @@ -80,7 +94,10 @@ macro_rules! impl_deserialize_for_int { None } }) - .ok_or(Error::InvalidType) + .ok_or_else(|| Error::InvalidType { + expected: stringify!($ty), + value: *value, + }) } } }; @@ -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", + value: *value, + }) } } 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", + value: *value, + }) } } @@ -114,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, + }) } } } @@ -128,7 +157,10 @@ impl Deserialize for Vec { } Ok(vec) } else { - Err(Error::InvalidType) + Err(Error::InvalidType { + expected: "array", + value: *value, + }) } } } @@ -136,13 +168,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", + value: *value, + }); }; 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", + value: *value, + })?; let value = value.get_at_index(i); map.insert(key, T::deserialize(&value)?); } @@ -171,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! { @@ -182,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") + ); + } } }; } @@ -204,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"); @@ -211,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| { @@ -220,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]); @@ -227,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!({ @@ -241,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" + ); + } }