#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Null,
Bool(bool),
I64(i64),
F64(f64),
String(Box<str>),
Array(Vec<Value>),
Object(Object),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Object {
pub entries: Vec<(StringKey, Value)>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct StringKey(Box<str>);
impl StringKey {
#[must_use]
pub fn new(s: &str) -> Self {
Self(s.into())
}
}
impl AsRef<str> for StringKey {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Object {
#[must_use]
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn insert(&mut self, key: &str, value: Value) {
match self.entries.iter_mut().find(|(k, _)| k.as_ref() == key) {
Some((_, v)) => *v = value,
None => self.entries.push((StringKey(key.into()), value)),
}
}
pub fn iter(&self) -> impl Iterator<Item = (&StringKey, &Value)> {
self.entries.iter().map(|(k, v)| (k, v))
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&Value> {
self.entries
.iter()
.find(|(k, _)| k.as_ref() == key)
.map(|(_, v)| v)
}
}
pub trait ToValue {
fn to_value(self) -> Value;
}
impl ToValue for i8 {
fn to_value(self) -> Value {
Value::I64(i64::from(self))
}
}
impl ToValue for i16 {
fn to_value(self) -> Value {
Value::I64(i64::from(self))
}
}
impl ToValue for i32 {
fn to_value(self) -> Value {
Value::I64(i64::from(self))
}
}
impl ToValue for i64 {
fn to_value(self) -> Value {
Value::I64(self)
}
}
impl ToValue for u8 {
fn to_value(self) -> Value {
Value::I64(i64::from(self))
}
}
impl ToValue for u16 {
fn to_value(self) -> Value {
Value::I64(i64::from(self))
}
}
impl ToValue for u32 {
fn to_value(self) -> Value {
Value::I64(i64::from(self))
}
}
impl ToValue for f32 {
fn to_value(self) -> Value {
Value::F64(f64::from(self))
}
}
impl ToValue for f64 {
fn to_value(self) -> Value {
Value::F64(self)
}
}
impl ToValue for String {
fn to_value(self) -> Value {
Value::String(self.into())
}
}
impl ToValue for bool {
fn to_value(self) -> Value {
Value::Bool(self)
}
}
impl Value {
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(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_i64(&self) -> Option<i64> {
match self {
Self::I64(i) => Some(*i),
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::F64(f) => Some(*f),
_ => None,
}
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Self::String(s.into())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Self::String(s.into())
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Self::Bool(b)
}
}
impl From<i64> for Value {
fn from(i: i64) -> Self {
Self::I64(i)
}
}
impl From<f64> for Value {
fn from(f: f64) -> Self {
Self::F64(f)
}
}
#[cfg(test)]
mod tests {
use super::{Object, ToValue, Value};
#[test]
fn d5_get_finds_present_key() {
let mut o = Object::new();
o.insert("name", Value::String("Ada".into()));
assert_eq!(o.get("name"), Some(&Value::String("Ada".into())));
}
#[test]
fn d5_get_misses_absent_key() {
let mut o = Object::new();
o.insert("name", Value::String("Ada".into()));
assert_eq!(o.get("age"), None);
}
#[test]
fn d5_get_on_empty_object() {
let o = Object::new();
assert_eq!(o.get("anything"), None);
}
#[test]
fn d4_tovalue_mappings_are_pinned() {
assert_eq!((-1i8).to_value(), Value::I64(-1));
assert_eq!((-2i16).to_value(), Value::I64(-2));
assert_eq!((-3i32).to_value(), Value::I64(-3));
assert_eq!((-4i64).to_value(), Value::I64(-4));
assert_eq!(1u8.to_value(), Value::I64(1));
assert_eq!(2u16.to_value(), Value::I64(2));
assert_eq!(3u32.to_value(), Value::I64(3));
assert_eq!(0.5f32.to_value(), Value::F64(0.5));
assert_eq!(1.5f64.to_value(), Value::F64(1.5));
assert_eq!(String::from("hi").to_value(), Value::String("hi".into()));
assert_eq!(true.to_value(), Value::Bool(true));
assert_eq!(false.to_value(), Value::Bool(false));
}
#[test]
fn dv1_duplicate_insert_last_write_wins_in_place() {
let mut o = Object::new();
o.insert("a", Value::I64(1));
o.insert("b", Value::I64(2));
o.insert("a", Value::I64(3));
assert_eq!(o.get("a"), Some(&Value::I64(3)), "last write wins");
assert_eq!(o.entries.len(), 2, "no duplicate entry appended");
let keys: Vec<&str> = o.iter().map(|(k, _)| k.as_ref()).collect();
assert_eq!(keys, vec!["a", "b"], "original position kept");
}
#[test]
fn dv1_ordered_entries_preserved() {
let mut o = Object::new();
o.insert("b", Value::I64(1));
o.insert("a", Value::I64(2));
o.insert("c", Value::I64(3));
let keys: Vec<&str> = o.iter().map(|(k, _)| k.as_ref()).collect();
assert_eq!(keys, vec!["b", "a", "c"]);
}
#[test]
fn dv1_number_fidelity_i64_max_and_f64() {
let v = Value::I64(i64::MAX);
assert_eq!(v.as_i64(), Some(i64::MAX));
let f = Value::F64(0.1);
assert_eq!(f.as_f64(), Some(0.1));
assert_eq!(f.as_i64(), None, "strict: no silent F64-to-I64 cast");
}
#[test]
fn accessor_strictness_no_silent_casts() {
assert_eq!(Value::String("x".into()).as_str(), Some("x"));
assert_eq!(Value::Bool(true).as_bool(), Some(true));
assert_eq!(Value::Null.as_str(), None);
assert_eq!(Value::F64(1.0).as_i64(), None);
assert_eq!(Value::I64(1).as_f64(), None, "strict: no silent I64-to-F64");
}
#[test]
fn from_impls_build_expected_variants() {
assert_eq!(Value::from("hi"), Value::String("hi".into()));
assert_eq!(Value::from(String::from("hi")), Value::String("hi".into()));
assert_eq!(Value::from(true), Value::Bool(true));
assert_eq!(Value::from(7i64), Value::I64(7));
assert_eq!(Value::from(0.5f64), Value::F64(0.5));
}
}
#[cfg(feature = "serde")]
pub mod json;