use crate::error::Result;
use crate::value::Value;
pub trait FromValue: Sized {
fn from_value(value: &Value) -> Result<Self>;
fn from_value_owned(value: Value) -> Result<Self> {
Self::from_value(&value)
}
}
impl FromValue for i64 {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::I64(v) => Ok(*v),
Value::Null => Err(crate::Error::TypeError("NULL value for i64".to_string())),
_ => Err(crate::Error::TypeError(format!(
"expected i64, got {:?}",
value
))),
}
}
}
impl FromValue for i32 {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::I32(v) => Ok(*v),
Value::I64(v) => (*v).try_into().map_err(|_| {
crate::Error::TypeError(format!("i64 value {} doesn't fit in i32", v))
}),
Value::Null => Err(crate::Error::TypeError("NULL value for i32".to_string())),
_ => Err(crate::Error::TypeError(format!(
"expected i32, got {:?}",
value
))),
}
}
}
impl FromValue for String {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::String(v) => Ok(v.clone()),
Value::Null => Err(crate::Error::TypeError("NULL value for String".to_string())),
_ => Err(crate::Error::TypeError(format!(
"expected String, got {:?}",
value
))),
}
}
fn from_value_owned(value: Value) -> Result<Self> {
match value {
Value::String(v) => Ok(v),
Value::Null => Err(crate::Error::TypeError("NULL value for String".to_string())),
other => Err(crate::Error::TypeError(format!(
"expected String, got {:?}",
other
))),
}
}
}
impl FromValue for bool {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Bool(v) => Ok(*v),
Value::I32(v) => match *v {
0 => Ok(false),
1 => Ok(true),
other => Err(crate::Error::TypeError(format!(
"expected bool-compatible i32 (0 or 1), got {}",
other
))),
},
Value::I64(v) => match *v {
0 => Ok(false),
1 => Ok(true),
other => Err(crate::Error::TypeError(format!(
"expected bool-compatible i64 (0 or 1), got {}",
other
))),
},
Value::Null => Err(crate::Error::TypeError("NULL value for bool".to_string())),
_ => Err(crate::Error::TypeError(format!(
"expected bool, got {:?}",
value
))),
}
}
}
impl FromValue for f64 {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::F64(v) => Ok(*v),
Value::Null => Err(crate::Error::TypeError("NULL value for f64".to_string())),
_ => Err(crate::Error::TypeError(format!(
"expected f64, got {:?}",
value
))),
}
}
}
impl FromValue for rust_decimal::Decimal {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Decimal(v) => Ok(*v),
Value::String(v) => v.parse::<rust_decimal::Decimal>().map_err(|e| {
crate::Error::TypeError(format!(
"failed to parse Decimal from string {:?}: {}",
v, e
))
}),
Value::Null => Err(crate::Error::TypeError(
"NULL value for Decimal".to_string(),
)),
_ => Err(crate::Error::TypeError(format!(
"expected Decimal, got {:?}",
value
))),
}
}
}
impl FromValue for chrono::NaiveDateTime {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::DateTime(v) => Ok(*v),
Value::String(v) => chrono::DateTime::parse_from_rfc3339(v)
.map(|dt| dt.naive_utc())
.or_else(|_| chrono::NaiveDateTime::parse_from_str(v, "%Y-%m-%dT%H:%M:%S%.f"))
.or_else(|_| chrono::NaiveDateTime::parse_from_str(v, "%Y-%m-%d %H:%M:%S%.f"))
.map_err(|_| {
crate::Error::TypeError(format!("failed to parse DateTime from string {:?}", v))
}),
Value::Null => Err(crate::Error::TypeError(
"NULL value for DateTime".to_string(),
)),
_ => Err(crate::Error::TypeError(format!(
"expected DateTime, got {:?}",
value
))),
}
}
}
impl FromValue for uuid::Uuid {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Uuid(v) => Ok(*v),
Value::String(v) => uuid::Uuid::parse_str(v).map_err(|e| {
crate::Error::TypeError(format!("failed to parse Uuid from string {:?}: {}", v, e))
}),
Value::Null => Err(crate::Error::TypeError("NULL value for Uuid".to_string())),
_ => Err(crate::Error::TypeError(format!(
"expected Uuid, got {:?}",
value
))),
}
}
}
impl FromValue for serde_json::Value {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Null => Err(crate::Error::TypeError("NULL value for Json".to_string())),
other => Ok(other.to_json_plain()),
}
}
fn from_value_owned(value: Value) -> Result<Self> {
match value {
Value::Null => Err(crate::Error::TypeError("NULL value for Json".to_string())),
other => Ok(other.to_json_plain()),
}
}
}
impl FromValue for Vec<u8> {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Bytes(v) => Ok(v.clone()),
Value::Null => Err(crate::Error::TypeError("NULL value for Bytes".to_string())),
_ => Err(crate::Error::TypeError(format!(
"expected Bytes, got {:?}",
value
))),
}
}
fn from_value_owned(value: Value) -> Result<Self> {
match value {
Value::Bytes(v) => Ok(v),
Value::Null => Err(crate::Error::TypeError("NULL value for Bytes".to_string())),
other => Err(crate::Error::TypeError(format!(
"expected Bytes, got {:?}",
other
))),
}
}
}
impl<T: FromValue> FromValue for Option<T> {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Null => Ok(None),
_ => T::from_value(value).map(Some),
}
}
fn from_value_owned(value: Value) -> Result<Self> {
match value {
Value::Null => Ok(None),
_ => T::from_value_owned(value).map(Some),
}
}
}
macro_rules! impl_vec_from_value {
($T:ty) => {
impl FromValue for Vec<$T> {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Array(items) => items.iter().map(<$T>::from_value).collect(),
Value::Json(json_value) => decode_json_array(json_value, <$T>::from_value),
Value::Null => Err(crate::Error::TypeError(
concat!("NULL value for Vec<", stringify!($T), ">").to_string(),
)),
_ => Err(crate::Error::TypeError(format!(
concat!(
"expected Array or Json for Vec<",
stringify!($T),
">, got {:?}"
),
value
))),
}
}
}
impl FromValue for Vec<Vec<$T>> {
fn from_value(value: &Value) -> Result<Self> {
match value {
Value::Array2D(rows) => rows
.iter()
.map(|row| row.iter().map(<$T>::from_value).collect())
.collect(),
Value::Json(json_value) => decode_json_2d_array(json_value, <$T>::from_value),
Value::Null => Err(crate::Error::TypeError(
concat!("NULL value for Vec<Vec<", stringify!($T), ">>").to_string(),
)),
_ => Err(crate::Error::TypeError(format!(
concat!(
"expected Array2D or Json for Vec<Vec<",
stringify!($T),
">>, got {:?}"
),
value
))),
}
}
}
};
}
impl_vec_from_value!(String);
impl_vec_from_value!(i32);
impl_vec_from_value!(i64);
impl_vec_from_value!(f64);
impl_vec_from_value!(bool);
impl_vec_from_value!(rust_decimal::Decimal);
impl_vec_from_value!(chrono::NaiveDateTime);
impl_vec_from_value!(uuid::Uuid);
impl_vec_from_value!(serde_json::Value);
fn decode_json_array<T, F>(json_value: &serde_json::Value, decoder: F) -> Result<Vec<T>>
where
F: Fn(&Value) -> Result<T>,
{
if let serde_json::Value::Array(arr) = json_value {
let mut result = Vec::with_capacity(arr.len());
for json_item in arr {
let value = crate::value::json_to_value_ref(json_item);
result.push(decoder(&value)?);
}
Ok(result)
} else {
Err(crate::Error::TypeError(format!(
"expected JSON array, got {:?}",
json_value
)))
}
}
fn decode_json_2d_array<T, F>(json_value: &serde_json::Value, decoder: F) -> Result<Vec<Vec<T>>>
where
F: Fn(&Value) -> Result<T>,
{
if let serde_json::Value::Array(outer_arr) = json_value {
let mut result = Vec::with_capacity(outer_arr.len());
for json_row in outer_arr {
if let serde_json::Value::Array(inner_arr) = json_row {
let mut row = Vec::with_capacity(inner_arr.len());
for json_item in inner_arr {
let value = crate::value::json_to_value_ref(json_item);
row.push(decoder(&value)?);
}
result.push(row);
} else {
return Err(crate::Error::TypeError(format!(
"expected inner JSON array, got {:?}",
json_row
)));
}
}
Ok(result)
} else {
Err(crate::Error::TypeError(format!(
"expected JSON 2D array, got {:?}",
json_value
)))
}
}
#[cfg(test)]
mod tests {
use super::FromValue;
use crate::Value;
#[test]
fn decimal_uuid_datetime_and_json_arrays_decode_from_json_storage() {
let decimals = Value::Json(serde_json::json!(["12.34", "56.78"]));
let uuids = Value::Json(serde_json::json!([
"550e8400-e29b-41d4-a716-446655440000",
"123e4567-e89b-12d3-a456-426614174000"
]));
let datetimes = Value::Json(serde_json::json!([
"2026-02-18T10:30:45Z",
"2026-02-19T11:31:46Z"
]));
let jsons = Value::Json(serde_json::json!([
{"name": "Alice"},
42,
true
]));
let decimals: Vec<rust_decimal::Decimal> = Vec::from_value(&decimals).unwrap();
let uuids: Vec<uuid::Uuid> = Vec::from_value(&uuids).unwrap();
let datetimes: Vec<chrono::NaiveDateTime> = Vec::from_value(&datetimes).unwrap();
let jsons: Vec<serde_json::Value> = Vec::from_value(&jsons).unwrap();
assert_eq!(decimals.len(), 2);
assert_eq!(uuids.len(), 2);
assert_eq!(datetimes.len(), 2);
assert_eq!(
jsons,
vec![
serde_json::json!({"name": "Alice"}),
serde_json::json!(42),
serde_json::json!(true),
]
);
}
#[test]
fn uuid_and_decimal_scalars_accept_string_values() {
let uuid = uuid::Uuid::from_value(&Value::String(
"550e8400-e29b-41d4-a716-446655440000".to_string(),
))
.unwrap();
let decimal =
rust_decimal::Decimal::from_value(&Value::String("12.34".to_string())).unwrap();
assert_eq!(uuid.to_string(), "550e8400-e29b-41d4-a716-446655440000");
assert_eq!(decimal.to_string(), "12.34");
}
}