#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
use serde_json::Value;
use crate::{ParseError, ToValueType};
pub trait ToNested<Type> {
fn to_nested<'a>(&'a self, path: &[&str]) -> Result<&'a Type, ParseError>;
}
impl ToNested<Value> for &Value {
fn to_nested<'a>(&'a self, path: &[&str]) -> Result<&'a Value, ParseError> {
get_nested_value(self, path)
}
}
pub fn get_nested_value<'a>(mut value: &'a Value, path: &[&str]) -> Result<&'a Value, ParseError> {
for (i, x) in path.iter().enumerate() {
if let Some(inner) = value.get(x) {
value = inner;
continue;
}
let message = if i > 0 {
format!("Path '{}' missing value: '{}'", path[..i].join(" -> "), x)
} else {
format!("Missing value: '{x}' ({value})")
};
return Err(ParseError::Parse(message));
}
Ok(value)
}
impl<'a> ToValueType<&'a str> for &'a Value {
fn to_value_type(self) -> Result<&'a str, ParseError> {
self.as_str()
.ok_or_else(|| ParseError::ConvertType("&str".into()))
}
}
impl<'a> ToValueType<&'a Value> for &'a Value {
fn to_value_type(self) -> Result<&'a Value, ParseError> {
Ok(self)
}
}
impl<'a, T> ToValueType<Option<T>> for &'a Value
where
&'a Value: ToValueType<T>,
{
fn to_value_type(self) -> Result<Option<T>, ParseError> {
self.to_value_type().map(|inner| Some(inner))
}
fn missing_value(&self, _error: ParseError) -> Result<Option<T>, ParseError> {
Ok(None)
}
}
impl<'a, T> ToValueType<Vec<T>> for &'a Value
where
&'a Value: ToValueType<T>,
{
fn to_value_type(self) -> Result<Vec<T>, ParseError> {
self.as_array()
.ok_or_else(|| ParseError::ConvertType("Vec<T>".into()))?
.iter()
.map(ToValueType::to_value_type)
.collect::<Result<Vec<_>, _>>()
}
}
impl ToValueType<String> for &Value {
fn to_value_type(self) -> Result<String, ParseError> {
Ok(self
.as_str()
.ok_or_else(|| ParseError::ConvertType("String".into()))?
.to_string())
}
}
impl ToValueType<bool> for &Value {
fn to_value_type(self) -> Result<bool, ParseError> {
self.as_bool()
.ok_or_else(|| ParseError::ConvertType("bool".into()))
}
}
impl ToValueType<f32> for &Value {
fn to_value_type(self) -> Result<f32, ParseError> {
Ok(self
.as_f64()
.ok_or_else(|| ParseError::ConvertType("f32".into()))? as f32)
}
}
impl ToValueType<f64> for &Value {
fn to_value_type(self) -> Result<f64, ParseError> {
self.as_f64()
.ok_or_else(|| ParseError::ConvertType("f64".into()))
}
}
impl ToValueType<u8> for &Value {
fn to_value_type(self) -> Result<u8, ParseError> {
Ok(self
.as_u64()
.ok_or_else(|| ParseError::ConvertType("u8".into()))? as u8)
}
}
impl ToValueType<u16> for &Value {
fn to_value_type(self) -> Result<u16, ParseError> {
Ok(self
.as_u64()
.ok_or_else(|| ParseError::ConvertType("u16".into()))? as u16)
}
}
impl ToValueType<u32> for &Value {
fn to_value_type(self) -> Result<u32, ParseError> {
Ok(self
.as_u64()
.ok_or_else(|| ParseError::ConvertType("u32".into()))? as u32)
}
}
impl ToValueType<u64> for &Value {
fn to_value_type(self) -> Result<u64, ParseError> {
self.as_u64()
.ok_or_else(|| ParseError::ConvertType("u64".into()))
}
}
impl ToValueType<usize> for &Value {
fn to_value_type(self) -> Result<usize, ParseError> {
self.as_u64()
.map(|x| x as usize)
.ok_or_else(|| ParseError::ConvertType("usize".into()))
}
}
impl ToValueType<i8> for &Value {
fn to_value_type(self) -> Result<i8, ParseError> {
Ok(self
.as_i64()
.ok_or_else(|| ParseError::ConvertType("i8".into()))? as i8)
}
}
impl ToValueType<i16> for &Value {
fn to_value_type(self) -> Result<i16, ParseError> {
Ok(self
.as_i64()
.ok_or_else(|| ParseError::ConvertType("i16".into()))? as i16)
}
}
impl ToValueType<i32> for &Value {
fn to_value_type(self) -> Result<i32, ParseError> {
Ok(self
.as_i64()
.ok_or_else(|| ParseError::ConvertType("i32".into()))? as i32)
}
}
impl ToValueType<i64> for &Value {
fn to_value_type(self) -> Result<i64, ParseError> {
self.as_i64()
.ok_or_else(|| ParseError::ConvertType("i64".into()))
}
}
impl ToValueType<isize> for &Value {
fn to_value_type(self) -> Result<isize, ParseError> {
self.as_i64()
.map(|x| x as isize)
.ok_or_else(|| ParseError::ConvertType("isize".into()))
}
}
pub trait ToValue {
fn to_value<'a, T>(&'a self, index: &str) -> Result<T, ParseError>
where
&'a Value: ToValueType<T>;
}
impl ToValue for Value {
fn to_value<'a, T>(&'a self, index: &str) -> Result<T, ParseError>
where
&'a Self: ToValueType<T>,
{
self.to_nested_value(&[index])
}
}
impl ToValue for &Value {
fn to_value<'a, T>(&'a self, index: &str) -> Result<T, ParseError>
where
&'a Value: ToValueType<T>,
{
self.to_nested_value(&[index])
}
}
pub trait ToNestedValue {
fn to_nested_value<'a, T>(&'a self, path: &[&str]) -> Result<T, ParseError>
where
&'a Value: ToValueType<T>;
}
impl ToNestedValue for Value {
fn to_nested_value<'a, T>(&'a self, path: &[&str]) -> Result<T, ParseError>
where
&'a Self: ToValueType<T>,
{
get_nested_value_type::<T>(self, path)
}
}
impl ToNestedValue for &Value {
fn to_nested_value<'a, T>(&'a self, path: &[&str]) -> Result<T, ParseError>
where
&'a Value: ToValueType<T>,
{
get_nested_value_type::<T>(self, path)
}
}
pub fn get_nested_value_type<'a, T>(value: &'a Value, path: &[&str]) -> Result<T, ParseError>
where
&'a Value: ToValueType<T>,
{
let mut inner_value = value;
for (i, x) in path.iter().enumerate() {
if let Some(inner) = inner_value.get(x) {
inner_value = inner;
continue;
}
let message = if i > 0 {
format!("Path '{}' missing value: '{x}'", path[..i].join(" -> "))
} else {
format!("Missing value: '{x}' ({value})")
};
return inner_value.missing_value(ParseError::Parse(message));
}
if inner_value.is_null() {
return inner_value.missing_value(ParseError::ConvertType(format!(
"{} found null",
path.join(" -> "),
)));
}
match inner_value.to_value_type() {
Ok(inner) => Ok(inner),
Err(err) => match err {
ParseError::ConvertType(_) => Err(ParseError::ConvertType(
if log::log_enabled!(log::Level::Debug) {
format!(
"Path '{}' failed to convert value to type: '{err:?}' ({})",
serde_json::to_string(value).unwrap_or_default(),
path.join(" -> "),
)
} else {
format!(
"Path '{}' failed to convert value to type: '{err:?}'",
path.join(" -> "),
)
},
)),
_ => Err(err),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_log::test]
fn test_to_nested_value_u64() {
let json = &serde_json::json!({
"outer": {
"inner_u64": 123,
},
});
assert_eq!(
json.to_nested_value::<u64>(&["outer", "inner_u64"])
.unwrap(),
123_u64
);
}
#[test_log::test]
fn test_to_value_option_null_string() {
let json = &serde_json::json!({
"str": serde_json::Value::Null,
});
assert_eq!(json.to_value::<Option<String>>("str").unwrap(), None);
}
#[test_log::test]
fn test_to_value_option_string() {
let json = &serde_json::json!({
"str": "hey there",
"u64": 123u64,
});
assert_eq!(
json.to_value::<Option<String>>("str").unwrap(),
Some("hey there".to_string())
);
assert_eq!(json.to_value::<Option<String>>("str2").unwrap(), None);
assert_eq!(
json.to_value::<Option<String>>("u64").err(),
Some(ParseError::ConvertType(
"Path 'u64' failed to convert value to type: 'ConvertType(\"String\")'".into()
)),
);
let result: Option<String> = json.to_value("str").unwrap();
assert_eq!(result, Some("hey there".to_string()));
let result: Option<String> = json.to_value("str2").unwrap();
assert_eq!(result, None);
}
#[test_log::test]
fn test_to_nested_value_option_u64() {
let json = &serde_json::json!({
"outer": {
"inner_u64": 123,
"inner_str": "hey there",
},
});
assert_eq!(
json.to_nested_value::<Option<u64>>(&["outer", "inner_u64"])
.unwrap(),
Some(123_u64)
);
assert_eq!(
json.to_nested_value::<Option<u64>>(&["outer", "bob"])
.unwrap(),
None
);
assert_eq!(
json.to_nested_value::<Option<u64>>(&["outer", "inner_str"])
.err(),
Some(ParseError::ConvertType(
"Path 'outer -> inner_str' failed to convert value to type: 'ConvertType(\"u64\")'"
.into()
)),
);
}
#[test_log::test]
fn test_to_nested_value_vec_u64() {
let json = &serde_json::json!({
"outer": {
"inner_u64_array": [123, 124, 125],
},
});
assert_eq!(
json.to_nested_value::<Vec<u64>>(&["outer", "inner_u64_array"])
.unwrap(),
vec![123_u64, 124_u64, 125_u64]
);
}
#[test_log::test]
fn test_to_value_nested_vec_u64() {
let json = &serde_json::json!({
"items": [
{"item": 123},
{"item": 124},
{"item": 125},
],
});
let values = json.to_value::<Vec<&Value>>("items").unwrap();
let numbers = values
.into_iter()
.map(|value| value.to_value::<u64>("item").unwrap())
.collect::<Vec<_>>();
assert_eq!(numbers, vec![123_u64, 124_u64, 125_u64]);
}
#[test_log::test]
fn test_get_nested_value_error_messages() {
let json = &serde_json::json!({
"level1": {
"level2": "value"
}
});
let result = get_nested_value(json, &["level1", "missing"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, ParseError::Parse(_)));
assert!(err.to_string().contains("level1"));
assert!(err.to_string().contains("missing"));
let result = get_nested_value(json, &["missing"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, ParseError::Parse(_)));
assert!(err.to_string().contains("missing"));
}
#[test_log::test]
fn test_to_nested_value_type_null_handling() {
let json = &serde_json::json!({
"outer": {
"inner": serde_json::Value::Null
}
});
let result = get_nested_value_type::<String>(json, &["outer", "inner"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, ParseError::ConvertType(_)));
assert!(err.to_string().contains("null"));
}
#[test_log::test]
fn test_to_value_type_conversions() {
let value = &serde_json::json!(42);
let result: Result<i8, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 42_i8);
let value = &serde_json::json!(1234);
let result: Result<i16, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 1234_i16);
let value = &serde_json::json!(123_456);
let result: Result<i32, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 123_456_i32);
let value = &serde_json::json!(123_456_789);
let result: Result<i64, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 123_456_789_i64);
let value = &serde_json::json!(12_345);
let result: Result<isize, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 12_345_isize);
}
#[test_log::test]
fn test_to_value_type_error_on_wrong_type() {
let value = &serde_json::json!("not a number");
let result: Result<u64, ParseError> = value.to_value_type();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
let value = &serde_json::json!(123);
let result: Result<String, ParseError> = value.to_value_type();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
let value = &serde_json::json!(1);
let result: Result<bool, ParseError> = value.to_value_type();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
}
#[test_log::test]
fn test_to_value_type_vec_error() {
let value = &serde_json::json!({"not": "an array"});
let result: Result<Vec<u64>, ParseError> = value.to_value_type();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
}
#[test_log::test]
fn test_to_value_identity() {
let value = &serde_json::json!({"key": "value"});
let result: Result<&Value, ParseError> = value.to_value_type();
assert!(result.is_ok());
}
#[test_log::test]
fn test_to_nested_value_deep_path() {
let json = &serde_json::json!({
"a": {
"b": {
"c": {
"d": 42
}
}
}
});
let result: Result<u64, ParseError> = json.to_nested_value(&["a", "b", "c", "d"]);
assert_eq!(result.unwrap(), 42);
}
#[test_log::test]
fn test_to_nested_value_with_value_ref() {
let json = serde_json::json!({
"nested": {
"value": "test"
}
});
let json_ref = &json;
let result: Result<String, ParseError> = json_ref.to_nested_value(&["nested", "value"]);
assert_eq!(result.unwrap(), "test");
}
#[test_log::test]
fn test_to_nested_missing_value_with_option() {
let json = &serde_json::json!({
"outer": {
"inner": "value"
}
});
let result: Result<Option<String>, ParseError> =
json.to_nested_value(&["outer", "missing"]);
assert_eq!(result.unwrap(), None);
}
#[test_log::test]
fn test_to_value_type_float_conversions() {
let value = &serde_json::json!(1.23456);
let result: Result<f32, ParseError> = value.to_value_type();
assert!((result.unwrap() - 1.23456_f32).abs() < 0.001);
let value = &serde_json::json!(1.234_567_89);
let result: Result<f64, ParseError> = value.to_value_type();
assert!((result.unwrap() - 1.234_567_89).abs() < f64::EPSILON);
let value = &serde_json::json!("not a number");
let result: Result<f32, ParseError> = value.to_value_type();
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
let result: Result<f64, ParseError> = value.to_value_type();
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
}
#[test_log::test]
fn test_to_nested_trait_direct_usage() {
let json = serde_json::json!({
"level1": {
"level2": {
"value": "found"
}
}
});
let json_ref = &json;
let result = json_ref.to_nested(&["level1", "level2", "value"]);
assert!(result.is_ok());
assert_eq!(result.unwrap().as_str(), Some("found"));
let result = json_ref.to_nested(&[]);
assert!(result.is_ok());
}
#[test_log::test]
fn test_get_nested_value_empty_path() {
let json = &serde_json::json!({"key": "value"});
let result = get_nested_value(json, &[]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), json);
}
#[test_log::test]
fn test_to_value_type_unsigned_integer_conversions() {
let value = &serde_json::json!(255);
let result: Result<u8, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 255_u8);
let result: Result<u16, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 255_u16);
let result: Result<u32, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 255_u32);
let result: Result<usize, ParseError> = value.to_value_type();
assert_eq!(result.unwrap(), 255_usize);
let value = &serde_json::json!("not a number");
let result: Result<u8, ParseError> = value.to_value_type();
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
let result: Result<usize, ParseError> = value.to_value_type();
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
}
#[test_log::test]
fn test_option_missing_value_returns_none() {
let value = &serde_json::json!(42);
let result = <&Value as ToValueType<Option<String>>>::missing_value(
&value,
ParseError::Parse("test".to_string()),
);
assert_eq!(result.unwrap(), None);
}
#[test_log::test]
fn test_to_value_with_value_ref() {
let json = serde_json::json!({
"key": "value"
});
let json_ref = &json;
let result: Result<String, ParseError> = json_ref.to_value("key");
assert_eq!(result.unwrap(), "value");
}
#[test_log::test]
fn test_vec_conversion_with_element_error() {
let value = &serde_json::json!([1, 2, "three"]);
let result: Result<Vec<u64>, ParseError> = value.to_value_type();
assert!(result.is_err());
}
#[test_log::test]
fn test_to_nested_value_type_with_parse_error() {
let json = &serde_json::json!({
"outer": {
"inner": "not_a_number"
}
});
let result = get_nested_value_type::<u64>(json, &["outer", "inner"]);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
}
#[test_log::test]
fn test_to_value_type_bool() {
let value = &serde_json::json!(true);
let result: Result<bool, ParseError> = value.to_value_type();
assert!(result.unwrap());
let value = &serde_json::json!(false);
let result: Result<bool, ParseError> = value.to_value_type();
assert!(!result.unwrap());
let value = &serde_json::json!("true");
let result: Result<bool, ParseError> = value.to_value_type();
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
}
#[test_log::test]
fn test_to_value_type_str_reference() {
let value = serde_json::json!("hello world");
let result: Result<&str, ParseError> = (&value).to_value_type();
assert_eq!(result.unwrap(), "hello world");
let value = &serde_json::json!(123);
let result: Result<&str, ParseError> = value.to_value_type();
assert!(matches!(result.unwrap_err(), ParseError::ConvertType(_)));
}
}