use serde_json::Value;
#[derive(Clone, Debug)]
pub enum BindValue {
Null,
Bool(bool),
I64(i64),
F64(f64),
String(String),
Uuid(uuid::Uuid),
Json(Value),
}
impl BindValue {
pub fn from_json(v: &Value) -> Result<Self, crate::error::AppError> {
Ok(match v {
Value::Null => BindValue::Null,
Value::Bool(b) => BindValue::Bool(*b),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
BindValue::I64(i)
} else if let Some(f) = n.as_f64() {
BindValue::F64(f)
} else {
BindValue::I64(0)
}
}
Value::String(s) => {
if let Ok(u) = uuid::Uuid::parse_str(s) {
BindValue::Uuid(u)
} else {
BindValue::String(s.clone())
}
}
Value::Array(_) | Value::Object(_) => BindValue::Json(v.clone()),
})
}
}
#[cfg(feature = "postgres")]
mod pg_impl {
use super::BindValue;
use sqlx::encode::{Encode, IsNull};
use sqlx::postgres::{PgTypeInfo, Postgres};
use sqlx::Database;
impl<'q> Encode<'q, Postgres> for BindValue {
fn encode_by_ref(
&self,
buf: &mut <Postgres as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, Box<dyn std::error::Error + Send + Sync>> {
Ok(match self {
BindValue::Null => <Option<i32> as Encode<Postgres>>::encode_by_ref(&None, buf)?,
BindValue::Bool(b) => {
let s: &str = if *b { "true" } else { "false" };
<&str as Encode<Postgres>>::encode_by_ref(&s, buf)?
}
BindValue::I64(n) => {
let s = n.to_string();
<&str as Encode<Postgres>>::encode_by_ref(&s.as_str(), buf)?
}
BindValue::F64(n) => {
let s = format!("{}", n);
<&str as Encode<Postgres>>::encode_by_ref(&s.as_str(), buf)?
}
BindValue::String(s) => {
<&str as Encode<Postgres>>::encode_by_ref(&s.as_str(), buf)?
}
BindValue::Uuid(u) => {
let s = u.to_string();
<&str as Encode<Postgres>>::encode_by_ref(&s.as_str(), buf)?
}
BindValue::Json(v) => {
let s = serde_json::to_string(v)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
<&str as Encode<Postgres>>::encode_by_ref(&s.as_str(), buf)?
}
})
}
}
impl sqlx::Type<Postgres> for BindValue {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_name("TEXT")
}
fn compatible(_ty: &PgTypeInfo) -> bool {
true
}
}
}
#[cfg(feature = "mysql")]
mod mysql_impl {
use super::BindValue;
use sqlx::encode::{Encode, IsNull};
use sqlx::mysql::{MySql, MySqlTypeInfo};
use sqlx::Database;
impl<'q> Encode<'q, MySql> for BindValue {
fn encode_by_ref(
&self,
buf: &mut <MySql as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, Box<dyn std::error::Error + Send + Sync>> {
Ok(match self {
BindValue::Null => <Option<i32> as Encode<MySql>>::encode_by_ref(&None, buf)?,
BindValue::Bool(b) => <i32 as Encode<MySql>>::encode_by_ref(&(*b as i32), buf)?,
BindValue::I64(n) => <i64 as Encode<MySql>>::encode_by_ref(n, buf)?,
BindValue::F64(n) => <f64 as Encode<MySql>>::encode_by_ref(n, buf)?,
BindValue::String(s) => <String as Encode<MySql>>::encode_by_ref(s, buf)?,
BindValue::Uuid(u) => {
let s = u.to_string();
<String as Encode<MySql>>::encode_by_ref(&s, buf)?
}
BindValue::Json(v) => {
let s = serde_json::to_string(v)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
<String as Encode<MySql>>::encode_by_ref(&s, buf)?
}
})
}
}
impl sqlx::Type<MySql> for BindValue {
fn type_info() -> MySqlTypeInfo {
<String as sqlx::Type<MySql>>::type_info()
}
}
}
#[cfg(feature = "sqlite")]
mod sqlite_impl {
use super::BindValue;
use sqlx::encode::{Encode, IsNull};
use sqlx::sqlite::{Sqlite, SqliteTypeInfo};
use sqlx::Database;
impl<'q> Encode<'q, Sqlite> for BindValue {
fn encode_by_ref(
&self,
buf: &mut <Sqlite as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, Box<dyn std::error::Error + Send + Sync>> {
Ok(match self {
BindValue::Null => <Option<i32> as Encode<Sqlite>>::encode_by_ref(&None, buf)?,
BindValue::Bool(b) => <i32 as Encode<Sqlite>>::encode_by_ref(&(*b as i32), buf)?,
BindValue::I64(n) => <i64 as Encode<Sqlite>>::encode_by_ref(n, buf)?,
BindValue::F64(n) => <f64 as Encode<Sqlite>>::encode_by_ref(n, buf)?,
BindValue::String(s) => <String as Encode<Sqlite>>::encode_by_ref(s, buf)?,
BindValue::Uuid(u) => {
let s = u.to_string();
<String as Encode<Sqlite>>::encode_by_ref(&s, buf)?
}
BindValue::Json(v) => {
let s = serde_json::to_string(v)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
<String as Encode<Sqlite>>::encode_by_ref(&s, buf)?
}
})
}
}
impl sqlx::Type<Sqlite> for BindValue {
fn type_info() -> SqliteTypeInfo {
<String as sqlx::Type<Sqlite>>::type_info()
}
}
}
#[cfg(feature = "postgres")]
pub type PgBindValue = BindValue;