#[derive(Debug, Clone, PartialEq, Default)]
pub enum Value {
#[default]
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
}
impl Value {
#[inline]
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
#[inline]
pub fn is_integer(&self) -> bool {
matches!(self, Value::Integer(_))
}
#[inline]
pub fn is_real(&self) -> bool {
matches!(self, Value::Real(_))
}
#[inline]
pub fn is_text(&self) -> bool {
matches!(self, Value::Text(_))
}
#[inline]
pub fn is_blob(&self) -> bool {
matches!(self, Value::Blob(_))
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Value::Integer(i) => Some(*i),
Value::Real(r) => Some(*r as i64),
Value::Text(s) => s.parse().ok(),
Value::Null | Value::Blob(_) => None,
}
}
pub fn as_real(&self) -> Option<f64> {
match self {
Value::Real(r) => Some(*r),
Value::Integer(i) => Some(*i as f64),
Value::Text(s) => s.parse().ok(),
Value::Null | Value::Blob(_) => None,
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
Value::Text(s) => Some(s),
_ => None,
}
}
pub fn as_blob(&self) -> Option<&[u8]> {
match self {
Value::Blob(b) => Some(b),
_ => None,
}
}
pub fn to_string_lossy(&self) -> String {
match self {
Value::Null => String::from("NULL"),
Value::Integer(i) => i.to_string(),
Value::Real(r) => r.to_string(),
Value::Text(s) => s.clone(),
Value::Blob(b) => format!("X'{}'", hex_encode(b)),
}
}
}
impl From<i64> for Value {
fn from(v: i64) -> Self {
Value::Integer(v)
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Value::Integer(v as i64)
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::Real(v)
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Value::Real(v as f64)
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::Text(v)
}
}
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::Text(v.to_string())
}
}
impl From<Vec<u8>> for Value {
fn from(v: Vec<u8>) -> Self {
Value::Blob(v)
}
}
impl From<&[u8]> for Value {
fn from(v: &[u8]) -> Self {
Value::Blob(v.to_vec())
}
}
impl<T> From<Option<T>> for Value
where
T: Into<Value>,
{
fn from(v: Option<T>) -> Self {
match v {
Some(val) => val.into(),
None => Value::Null,
}
}
}
fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02X}", b)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_types() {
assert!(Value::Null.is_null());
assert!(Value::Integer(42).is_integer());
assert!(Value::Real(3.14).is_real());
assert!(Value::Text("hello".into()).is_text());
assert!(Value::Blob(vec![1, 2, 3]).is_blob());
}
#[test]
fn test_integer_coercion() {
assert_eq!(Value::Integer(42).as_integer(), Some(42));
assert_eq!(Value::Real(3.7).as_integer(), Some(3));
assert_eq!(Value::Text("123".into()).as_integer(), Some(123));
assert_eq!(Value::Null.as_integer(), None);
}
#[test]
fn test_from_conversions() {
let v: Value = 42i64.into();
assert_eq!(v, Value::Integer(42));
let v: Value = 3.14f64.into();
assert_eq!(v, Value::Real(3.14));
let v: Value = "hello".into();
assert_eq!(v, Value::Text("hello".to_string()));
let v: Value = None::<i64>.into();
assert_eq!(v, Value::Null);
}
}