use std::sync::Arc;
use serde::{Deserialize, Serialize};
pub type Map = Vec<(Arc<str>, Value)>;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind", content = "value")]
#[non_exhaustive]
pub enum Value {
Null,
Bool(bool),
Int(i64),
Number(f64),
Str(Arc<str>),
List(Vec<Value>),
Map(Map),
}
impl Value {
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Str(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match *self {
Self::Bool(b) => Some(b),
_ => None,
}
}
#[must_use]
pub fn as_int(&self) -> Option<i64> {
match *self {
Self::Int(n) => Some(n),
_ => None,
}
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
mod tests {
use super::*;
#[test]
fn test_should_round_trip_value_via_serde_json() {
let v = Value::Map(vec![
(Arc::from("a"), Value::Int(42)),
(Arc::from("b"), Value::Str(Arc::from("hello"))),
(
Arc::from("c"),
Value::List(vec![Value::Bool(true), Value::Null]),
),
]);
let json = serde_json::to_string(&v).unwrap();
let back: Value = serde_json::from_str(&json).unwrap();
assert_eq!(v, back);
}
#[test]
fn test_should_offer_typed_accessors() {
assert_eq!(Value::Str(Arc::from("x")).as_str(), Some("x"));
assert_eq!(Value::Bool(true).as_bool(), Some(true));
assert_eq!(Value::Int(7).as_int(), Some(7));
assert!(Value::Null.is_null());
}
#[test]
fn test_should_preserve_map_insertion_order() {
let m: Map = vec![
(Arc::from("z"), Value::Int(1)),
(Arc::from("a"), Value::Int(2)),
(Arc::from("m"), Value::Int(3)),
];
let Value::Map(got) = Value::Map(m) else {
panic!("constructed a Map, did not get one back");
};
let keys: Vec<&str> = got.iter().map(|(k, _)| k.as_ref()).collect();
assert_eq!(keys, vec!["z", "a", "m"]);
}
}