use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum FieldPresence {
Present(Value),
Null,
Absent,
}
impl FieldPresence {
#[must_use]
pub const fn is_present(&self) -> bool {
matches!(self, Self::Present(_))
}
#[must_use]
pub const fn is_absent(&self) -> bool {
matches!(self, Self::Absent)
}
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub const fn as_value(&self) -> Option<&Value> {
match self {
Self::Present(v) => Some(v),
Self::Null | Self::Absent => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Value {
Bool(bool),
Int(i64),
Float(f64),
Str(String),
Bytes(Vec<u8>),
CidLink(String),
Blob {
ref_: String,
mime: String,
size: u64,
},
Token(String),
Null,
Opaque {
type_: String,
fields: HashMap<String, Self>,
},
Unknown(HashMap<String, Self>),
List(Vec<Self>),
}
impl Value {
#[must_use]
pub const fn type_name(&self) -> &'static str {
match self {
Self::Bool(_) => "bool",
Self::Int(_) => "int",
Self::Float(_) => "float",
Self::Str(_) => "str",
Self::Bytes(_) => "bytes",
Self::CidLink(_) => "cid-link",
Self::Blob { .. } => "blob",
Self::Token(_) => "token",
Self::Null => "null",
Self::Opaque { .. } => "opaque",
Self::Unknown(_) => "unknown",
Self::List(_) => "list",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn field_presence_predicates() {
let present = FieldPresence::Present(Value::Int(42));
assert!(present.is_present());
assert!(!present.is_null());
assert!(!present.is_absent());
let null = FieldPresence::Null;
assert!(null.is_null());
let absent = FieldPresence::Absent;
assert!(absent.is_absent());
}
#[test]
fn value_type_names() {
assert_eq!(Value::Bool(true).type_name(), "bool");
assert_eq!(Value::Str("hello".into()).type_name(), "str");
assert_eq!(Value::Null.type_name(), "null");
assert_eq!(Value::List(Vec::new()).type_name(), "list");
assert_eq!(Value::Unknown(HashMap::new()).type_name(), "unknown");
}
#[test]
fn value_list_round_trip_via_serde() -> Result<(), serde_json::Error> {
let original = Value::List(vec![
Value::Int(1),
Value::Str("two".into()),
Value::Bool(true),
]);
let json = serde_json::to_string(&original)?;
let restored: Value = serde_json::from_str(&json)?;
assert_eq!(original, restored);
Ok(())
}
#[test]
fn value_list_is_free_monoid_over_values() {
let a = Value::List(vec![Value::Int(1), Value::Int(2)]);
let b = Value::List(vec![Value::Int(3)]);
let empty = Value::List(Vec::new());
let concat = |xs: &Value, ys: &Value| match (xs, ys) {
(Value::List(x), Value::List(y)) => {
let mut out = x.clone();
out.extend(y.iter().cloned());
Value::List(out)
}
_ => panic!("expected lists"),
};
assert_eq!(
concat(&a, &b),
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
assert_eq!(concat(&empty, &a), a, "left identity");
assert_eq!(concat(&a, &empty), a, "right identity");
}
#[test]
fn field_presence_as_value() {
let present = FieldPresence::Present(Value::Int(42));
assert_eq!(present.as_value(), Some(&Value::Int(42)));
let null = FieldPresence::Null;
assert_eq!(null.as_value(), None);
}
}