use std::collections::HashMap;
use crate::ServiceError;
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Value {
Null,
Bool(bool),
I32(i32),
U32(u32),
F64(f64),
String(String),
Bytes(Vec<u8>),
Array(Vec<Value>),
Map(HashMap<String, Value>),
}
impl Value {
pub fn as_u32(&self) -> Result<u32, ServiceError> {
match self {
Value::U32(n) => Ok(*n),
Value::I32(n) if *n >= 0 => Ok(*n as u32),
Value::F64(n) if *n >= 0.0 && *n <= f64::from(u32::MAX) => Ok(*n as u32),
Value::String(s) => s.parse::<u32>().map_err(|e| {
ServiceError::TypeMismatch {
expected: "u32".into(),
found: format!("string \"{s}\" ({e})"),
}
}),
other => Err(ServiceError::TypeMismatch {
expected: "u32".into(),
found: format!("{other:?}"),
}),
}
}
pub fn as_i32(&self) -> Result<i32, ServiceError> {
match self {
Value::I32(n) => Ok(*n),
Value::U32(n) if i32::try_from(*n).is_ok() => Ok(*n as i32),
Value::F64(n) if *n >= f64::from(i32::MIN) && *n <= f64::from(i32::MAX) => {
Ok(*n as i32)
}
Value::String(s) => s.parse::<i32>().map_err(|e| {
ServiceError::TypeMismatch {
expected: "i32".into(),
found: format!("string \"{s}\" ({e})"),
}
}),
other => Err(Self::type_mismatch("i32", other)),
}
}
pub fn as_f64(&self) -> Result<f64, ServiceError> {
match self {
Value::F64(n) => Ok(*n),
Value::I32(n) => Ok(f64::from(*n)),
Value::U32(n) => Ok(f64::from(*n)),
Value::String(s) => s.parse::<f64>().map_err(|e| {
ServiceError::TypeMismatch {
expected: "f64".into(),
found: format!("string \"{s}\" ({e})"),
}
}),
other => Err(Self::type_mismatch("f64", other)),
}
}
pub fn as_str(&self) -> Result<&str, ServiceError> {
match self {
Value::String(s) => Ok(s.as_str()),
other => Err(Self::type_mismatch("string", other)),
}
}
pub fn as_bool(&self) -> Result<bool, ServiceError> {
match self {
Value::Bool(b) => Ok(*b),
Value::U32(n) => Ok(*n != 0),
Value::I32(n) => Ok(*n != 0),
Value::String(s) if s == "true" || s == "1" => Ok(true),
Value::String(s) if s == "false" || s == "0" => Ok(false),
other => Err(Self::type_mismatch("bool", other)),
}
}
pub fn as_bytes(&self) -> Result<&[u8], ServiceError> {
match self {
Value::Bytes(b) => Ok(b.as_slice()),
other => Err(Self::type_mismatch("bytes", other)),
}
}
pub fn as_array(&self) -> Result<&[Value], ServiceError> {
match self {
Value::Array(arr) => Ok(arr.as_slice()),
other => Err(Self::type_mismatch("array", other)),
}
}
pub fn as_map(&self) -> Result<&HashMap<String, Value>, ServiceError> {
match self {
Value::Map(map) => Ok(map),
other => Err(Self::type_mismatch("map", other)),
}
}
pub fn get(&self, key: &str) -> Result<&Value, ServiceError> {
match self {
Value::Map(map) => {
map.get(key).ok_or_else(|| ServiceError::TypeMismatch {
expected: format!("map with key \"{key}\""),
found: format!("{self:?}"),
})
}
other => Err(Self::type_mismatch("map", other)),
}
}
pub(crate) fn type_mismatch(expected: &str, found: &Self) -> ServiceError {
ServiceError::TypeMismatch {
expected: expected.into(),
found: format!("{found:?}"),
}
}
}
impl From<u32> for Value {
fn from(v: u32) -> Self {
Value::U32(v)
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Value::I32(v)
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::F64(v)
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Bool(v)
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::String(v)
}
}
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::String(v.to_owned())
}
}
impl From<Vec<u8>> for Value {
fn from(v: Vec<u8>) -> Self {
Value::Bytes(v)
}
}
impl From<Vec<Value>> for Value {
fn from(v: Vec<Value>) -> Self {
Value::Array(v)
}
}
impl<K, V> From<HashMap<K, V>> for Value
where
K: Into<String>,
V: Into<Value>,
{
fn from(map: HashMap<K, V>) -> Self {
Value::Map(map.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_as_u32_from_u32() {
assert_eq!(Value::U32(42).as_u32().unwrap(), 42);
}
#[test]
fn test_as_u32_from_i32_positive() {
assert_eq!(Value::I32(42).as_u32().unwrap(), 42);
}
#[test]
fn test_as_u32_from_i32_negative() {
assert!(Value::I32(-1).as_u32().is_err());
}
#[test]
fn test_as_u32_from_string() {
assert_eq!(Value::String("42".into()).as_u32().unwrap(), 42);
}
#[test]
fn test_as_u32_from_string_invalid() {
assert!(Value::String("hello".into()).as_u32().is_err());
}
#[test]
fn test_as_u32_type_mismatch() {
let err = Value::Bool(true).as_u32().unwrap_err();
assert!(matches!(err, ServiceError::TypeMismatch { .. }));
}
#[test]
fn test_as_str_ok() {
assert_eq!(Value::String("hello".into()).as_str().unwrap(), "hello");
}
#[test]
fn test_as_str_type_mismatch() {
assert!(Value::U32(42).as_str().is_err());
}
#[test]
fn test_as_array_ok() {
let arr = Value::Array(vec![Value::U32(1), Value::U32(2)]);
assert_eq!(arr.as_array().unwrap().len(), 2);
}
#[test]
fn test_as_array_empty() {
let arr = Value::Array(vec![]);
assert!(arr.as_array().unwrap().is_empty());
}
#[test]
fn test_get_from_map() {
let mut map = HashMap::new();
map.insert("id".into(), Value::U32(42));
let val = Value::Map(map);
assert_eq!(val.get("id").unwrap().as_u32().unwrap(), 42);
}
#[test]
fn test_get_missing_key() {
let map = Value::Map(HashMap::new());
assert!(map.get("missing").is_err());
}
#[test]
fn test_get_from_non_map() {
assert!(Value::U32(42).get("id").is_err());
}
#[test]
fn test_from_primitives() {
assert_eq!(Value::from(42u32), Value::U32(42));
assert_eq!(Value::from(-1i32), Value::I32(-1));
assert_eq!(Value::from(3.14f64), Value::F64(3.14));
assert_eq!(Value::from(true), Value::Bool(true));
assert_eq!(Value::from("hello"), Value::String("hello".into()));
}
#[test]
fn test_as_i32_from_i32() {
assert_eq!(Value::I32(42).as_i32().unwrap(), 42);
assert_eq!(Value::I32(-5).as_i32().unwrap(), -5);
}
#[test]
fn test_as_i32_from_u32() {
assert_eq!(Value::U32(42).as_i32().unwrap(), 42);
assert!(Value::U32(u32::MAX).as_i32().is_err());
}
#[test]
fn test_as_f64_from_f64() {
assert!((Value::F64(3.14).as_f64().unwrap() - 3.14).abs() < 1e-10);
}
#[test]
fn test_as_f64_from_i32() {
assert!((Value::I32(5).as_f64().unwrap() - 5.0).abs() < 1e-10);
}
#[test]
fn test_as_bytes() {
let bytes = vec![1u8, 2, 3];
assert_eq!(Value::Bytes(bytes).as_bytes().unwrap(), &[1, 2, 3]);
assert!(Value::U32(42).as_bytes().is_err());
}
#[test]
fn test_as_map_ok() {
let mut map = HashMap::new();
map.insert("a".into(), Value::U32(1));
map.insert("b".into(), Value::U32(2));
let val = Value::Map(map);
let m = val.as_map().unwrap();
assert!(m.contains_key("a"));
assert!(m.contains_key("b"));
}
#[test]
fn test_as_map_type_mismatch() {
assert!(Value::U32(42).as_map().is_err());
}
#[test]
fn test_as_bool_from_bool() {
assert_eq!(Value::Bool(true).as_bool().unwrap(), true);
}
#[test]
fn test_as_bool_from_u32() {
assert_eq!(Value::U32(1).as_bool().unwrap(), true);
assert_eq!(Value::U32(0).as_bool().unwrap(), false);
}
#[test]
fn test_as_bool_from_string() {
assert_eq!(Value::String("true".into()).as_bool().unwrap(), true);
assert_eq!(Value::String("false".into()).as_bool().unwrap(), false);
}
}