Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion api/examples/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ impl Deserialize for Value {
}
Ok(Value::Array(arr))
} else {
Err(ReadError::InvalidType)
Err(ReadError::InvalidType {
expected: "value",
value: *value,
})
}
}
}
Expand Down
31 changes: 30 additions & 1 deletion api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ impl CachedInternedStringId {
/// - object
/// - array
/// - error
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub struct Value {
context: NonNull<ContextPtr>,
nan_box: NanBox,
Expand Down Expand Up @@ -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.
Expand Down
150 changes: 139 additions & 11 deletions api/src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::error::Error + Send + Sync>),
}

/// A trait for types that can be deserialized from a [`Value`].
Expand Down Expand Up @@ -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<Self, Error> {
value.as_bool().ok_or(Error::InvalidType)
value.as_bool().ok_or_else(|| Error::InvalidType {
expected: "boolean",
value: *value,
})
}
}

Expand All @@ -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,
})
}
}
};
Expand All @@ -99,13 +116,19 @@ impl_deserialize_for_int!(isize);

impl Deserialize for f64 {
fn deserialize(value: &Value) -> Result<Self, Error> {
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<Self, Error> {
value.as_string().ok_or(Error::InvalidType)
value.as_string().ok_or_else(|| Error::InvalidType {
expected: "string",
value: *value,
})
}
}

Expand All @@ -114,7 +137,13 @@ impl<T: Deserialize> Deserialize for Option<T> {
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::<Option<T>>(),
value,
},
e => e,
})
}
}
}
Expand All @@ -128,21 +157,32 @@ impl<T: Deserialize> Deserialize for Vec<T> {
}
Ok(vec)
} else {
Err(Error::InvalidType)
Err(Error::InvalidType {
expected: "array",
value: *value,
})
}
}
}

impl<T: Deserialize> Deserialize for HashMap<String, T> {
fn deserialize(value: &Value) -> Result<Self, Error> {
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)?);
}
Expand Down Expand Up @@ -171,6 +211,17 @@ mod tests {
});
}

#[test]
fn test_deserialize_bool_error() {
let value = serde_json::json!(1);
let result = deserialize_json_value::<bool>(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! {
Expand All @@ -182,6 +233,17 @@ mod tests {
assert_eq!(result, n);
});
}

#[test]
fn [<test_deserialize_ $ty _error>]() {
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")
);
}
}
};
}
Expand All @@ -204,13 +266,35 @@ mod tests {
assert_eq!(result, 1.0);
}

#[test]
fn test_deserialize_f64_error() {
let value = serde_json::json!(null);
let result = deserialize_json_value::<f64>(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");
let result: String = deserialize_json_value(value).unwrap();
assert_eq!(result, "test");
}

#[test]
fn test_deserialize_string_error() {
let value = serde_json::json!(null);
let result = deserialize_json_value::<String>(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| {
Expand All @@ -220,13 +304,35 @@ mod tests {
});
}

#[test]
fn test_deserialize_option_error() {
let value = serde_json::json!("test");
let result = deserialize_json_value::<Option<i32>>(value);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid type; expected core::option::Option<i32>, got string"
);
}

#[test]
fn test_deserialize_vec() {
let value = serde_json::json!([1, 2, 3]);
let result: Vec<i32> = deserialize_json_value(value).unwrap();
assert_eq!(result, vec![1, 2, 3]);
}

#[test]
fn test_deserialize_vec_error() {
let value = serde_json::json!("test");
let result = deserialize_json_value::<Vec<i32>>(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!({
Expand All @@ -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::<HashMap<String, String>>(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"
);
}
}
Loading