use std::fmt;
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn to_literal(&self) -> String {
match self {
Value::Null => "NULL".to_owned(),
Value::Integer(n) => n.to_string(),
Value::Real(f) => {
if f.fract() == 0.0 && f.is_finite() {
format!("{f:.1}")
} else {
f.to_string()
}
}
Value::Text(s) => format!("'{}'", s.replace('\'', "''")),
Value::Blob(b) => {
use std::fmt::Write as _;
let mut out = String::with_capacity(b.len() * 2 + 3);
out.push_str("X'");
for byte in b {
let _ = write!(out, "{byte:02X}");
}
out.push('\'');
out
}
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_literal())
}
}
macro_rules! int_from {
($($t:ty),*) => {$(
impl From<$t> for Value {
fn from(v: $t) -> Self {
Value::Integer(i64::from(v))
}
}
)*};
}
int_from!(i8, i16, i32, i64, u8, u16, u32);
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Integer(i64::from(v))
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Value::Real(f64::from(v))
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::Real(v)
}
}
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::Text(v.to_owned())
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::Text(v)
}
}
impl From<&String> for Value {
fn from(v: &String) -> Self {
Value::Text(v.clone())
}
}
impl From<char> for Value {
fn from(v: char) -> 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: Into<Value>> From<Option<T>> for Value {
fn from(v: Option<T>) -> Self {
v.map_or(Value::Null, Into::into)
}
}
impl<T: Into<Value> + Clone> From<&T> for Value
where
T: ValueRefInto,
{
fn from(v: &T) -> Self {
v.clone().into()
}
}
pub trait ValueRefInto {}
impl ValueRefInto for bool {}
impl ValueRefInto for i8 {}
impl ValueRefInto for i16 {}
impl ValueRefInto for i32 {}
impl ValueRefInto for i64 {}
impl ValueRefInto for u8 {}
impl ValueRefInto for u16 {}
impl ValueRefInto for u32 {}
impl ValueRefInto for f32 {}
impl ValueRefInto for f64 {}
impl ValueRefInto for Vec<u8> {}
impl<T: ValueRefInto> ValueRefInto for Option<T> {}
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
mod chrono_impls {
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use super::Value;
pub const NAIVE_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.f";
impl From<NaiveDate> for Value {
fn from(v: NaiveDate) -> Self {
Value::Text(v.format("%Y-%m-%d").to_string())
}
}
impl From<NaiveTime> for Value {
fn from(v: NaiveTime) -> Self {
Value::Text(v.format("%H:%M:%S%.f").to_string())
}
}
impl From<NaiveDateTime> for Value {
fn from(v: NaiveDateTime) -> Self {
Value::Text(v.format(NAIVE_DATETIME_FORMAT).to_string())
}
}
impl<Tz: TimeZone> From<DateTime<Tz>> for Value {
fn from(v: DateTime<Tz>) -> Self {
Value::Text(v.to_rfc3339())
}
}
impl super::ValueRefInto for NaiveDate {}
impl super::ValueRefInto for NaiveTime {}
impl super::ValueRefInto for NaiveDateTime {}
impl super::ValueRefInto for DateTime<Utc> {}
impl super::ValueRefInto for DateTime<FixedOffset> {}
}
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
pub use chrono_impls::NAIVE_DATETIME_FORMAT;
#[cfg(feature = "with-uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))]
impl From<uuid::Uuid> for Value {
fn from(v: uuid::Uuid) -> Self {
Value::Text(v.hyphenated().to_string())
}
}
#[cfg(feature = "with-uuid")]
impl ValueRefInto for uuid::Uuid {}
#[cfg(feature = "with-json")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
impl From<serde_json::Value> for Value {
fn from(v: serde_json::Value) -> Self {
Value::Text(v.to_string())
}
}
#[cfg(feature = "with-json")]
impl ValueRefInto for serde_json::Value {}
#[cfg(feature = "with-rust_decimal")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-rust_decimal")))]
impl From<rust_decimal::Decimal> for Value {
fn from(v: rust_decimal::Decimal) -> Self {
Value::Text(v.to_string())
}
}
#[cfg(feature = "with-rust_decimal")]
impl ValueRefInto for rust_decimal::Decimal {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn literals() {
assert_eq!(Value::from(true).to_literal(), "1");
assert_eq!(Value::from("it's").to_literal(), "'it''s'");
assert_eq!(Value::from(vec![0xAB, 0x01]).to_literal(), "X'AB01'");
assert_eq!(Value::from(Option::<i32>::None), Value::Null);
assert_eq!(Value::from(2.0f64).to_literal(), "2.0");
}
}