use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Value<'a> {
#[default]
Null,
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
Str(&'a str),
Char(char),
}
impl<'a> Value<'a> {
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub const fn variant_name(&self) -> &'static str {
match self {
Self::Null => "null",
Self::Bool(_) => "bool",
Self::I64(_) => "i64",
Self::U64(_) => "u64",
Self::F64(_) => "f64",
Self::Str(_) => "str",
Self::Char(_) => "char",
}
}
}
impl<'a> fmt::Display for Value<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => f.write_str("null"),
Self::Bool(b) => fmt::Display::fmt(b, f),
Self::I64(n) => fmt::Display::fmt(n, f),
Self::U64(n) => fmt::Display::fmt(n, f),
Self::F64(n) => fmt::Display::fmt(n, f),
Self::Str(s) => f.write_str(s),
Self::Char(c) => fmt::Display::fmt(c, f),
}
}
}
impl<'a> From<&'a str> for Value<'a> {
fn from(value: &'a str) -> Self {
Self::Str(value)
}
}
impl From<bool> for Value<'_> {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<char> for Value<'_> {
fn from(value: char) -> Self {
Self::Char(value)
}
}
macro_rules! from_int {
($($ty:ty => $variant:ident),* $(,)?) => {
$(
impl From<$ty> for Value<'_> {
fn from(value: $ty) -> Self {
#[allow(clippy::cast_lossless)]
Self::$variant(value as _)
}
}
)*
};
}
from_int!(
i8 => I64, i16 => I64, i32 => I64, i64 => I64, isize => I64,
u8 => U64, u16 => U64, u32 => U64, u64 => U64, usize => U64,
);
impl From<f32> for Value<'_> {
fn from(value: f32) -> Self {
Self::F64(f64::from(value))
}
}
impl From<f64> for Value<'_> {
fn from(value: f64) -> Self {
Self::F64(value)
}
}
impl<'a, T> From<Option<T>> for Value<'a>
where
T: Into<Value<'a>>,
{
fn from(value: Option<T>) -> Self {
match value {
Some(v) => v.into(),
None => Self::Null,
}
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn from_str_is_borrowed() {
let s = String::from("hello");
let v: Value<'_> = s.as_str().into();
assert_eq!(v, Value::Str("hello"));
}
#[test]
fn integer_conversions() {
let v: Value<'_> = 42_i32.into();
assert_eq!(v, Value::I64(42));
let v: Value<'_> = 42_u32.into();
assert_eq!(v, Value::U64(42));
let v: Value<'_> = (-1_i64).into();
assert_eq!(v, Value::I64(-1));
}
#[test]
fn option_into_value() {
let some: Value<'_> = Some(7_u32).into();
assert_eq!(some, Value::U64(7));
let none: Value<'_> = Option::<u32>::None.into();
assert!(none.is_null());
}
#[test]
fn variant_name_covers_all() {
for v in [
Value::Null,
Value::Bool(true),
Value::I64(0),
Value::U64(0),
Value::F64(0.0),
Value::Str(""),
Value::Char('a'),
] {
assert!(!v.variant_name().is_empty());
}
}
#[test]
fn display_renders_each_variant() {
assert_eq!(Value::Null.to_string(), "null");
assert_eq!(Value::Bool(true).to_string(), "true");
assert_eq!(Value::I64(-5).to_string(), "-5");
assert_eq!(Value::U64(5).to_string(), "5");
assert_eq!(Value::Str("x").to_string(), "x");
assert_eq!(Value::Char('y').to_string(), "y");
}
}