use crate::pool::Engine;
use crate::value::{ColumnKind, SqlValue, SqlxValue, ValueTypeError};
use alibi_core::error::AuthResult;
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
#[cfg(feature = "postgres")]
use sqlx::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres};
#[cfg(feature = "sqlite")]
use sqlx::sqlite::{Sqlite, SqliteArgumentsBuffer, SqliteTypeInfo, SqliteValueRef};
use sqlx::{Decode, Encode, Type, ValueRef};
#[derive(Clone, Debug, serde::Serialize)]
#[serde(transparent)]
pub struct JsonMetadata(serde_json::Value, #[serde(skip)] Option<String>);
impl JsonMetadata {
pub(crate) fn for_backend(
value: serde_json::Value,
backend: Engine,
) -> Result<Self, serde_json::Error> {
let text = if backend == Engine::Sqlite {
Some(alibi_core::utils::json::to_string(&value)?)
} else {
None
};
Ok(Self(value, text))
}
#[cfg(feature = "sqlite")]
fn sqlite_text(&self) -> Result<String, serde_json::Error> {
match &self.1 {
Some(text) => Ok(text.clone()),
None => serde_json::to_string(&self.0),
}
}
}
impl PartialEq for JsonMetadata {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<'de> serde::Deserialize<'de> for JsonMetadata {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
alibi_core::utils::json::deserialize_value(deserializer).map(Self::from)
}
}
impl From<serde_json::Value> for JsonMetadata {
fn from(value: serde_json::Value) -> Self {
Self(value, None)
}
}
impl From<JsonMetadata> for serde_json::Value {
fn from(value: JsonMetadata) -> Self {
value.0
}
}
impl std::ops::Deref for JsonMetadata {
type Target = serde_json::Value;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl SqlxValue for JsonMetadata {
const KIND: ColumnKind = ColumnKind::Json;
fn null() -> SqlValue {
SqlValue::Json(None)
}
fn into_sql_value(self) -> SqlValue {
match self.1 {
Some(text) => SqlValue::Text(Some(text)),
None => SqlValue::Json(Some(Box::new(self.0))),
}
}
fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
match value {
SqlValue::Json(Some(value)) => Ok(Self::from(*value)),
SqlValue::Text(Some(text)) => {
alibi_core::utils::json::from_slice::<serde_json::Value>(text.as_bytes())
.map(Self::from)
.map_err(|_error| ValueTypeError)
}
_ => Err(ValueTypeError),
}
}
fn prepare(self, engine: Engine) -> AuthResult<Self> {
let value = alibi_core::utils::json::to_value(&self.0)?;
Ok(Self::for_backend(value, engine)?)
}
}
#[cfg(feature = "sqlite")]
impl Type<Sqlite> for JsonMetadata {
fn type_info() -> SqliteTypeInfo {
<sqlx::types::Json<Self> as Type<Sqlite>>::type_info()
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<sqlx::types::Json<Self> as Type<Sqlite>>::compatible(ty)
}
}
#[cfg(feature = "sqlite")]
impl<'r> Decode<'r, Sqlite> for JsonMetadata {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
<sqlx::types::Json<Self> as Decode<'r, Sqlite>>::decode(value).map(|json| json.0)
}
}
#[cfg(feature = "sqlite")]
impl Encode<'_, Sqlite> for JsonMetadata {
fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
<String as Encode<'_, Sqlite>>::encode(self.sqlite_text()?, buf)
}
}
#[cfg(feature = "postgres")]
impl Type<Postgres> for JsonMetadata {
fn type_info() -> PgTypeInfo {
<sqlx::types::Json<Self> as Type<Postgres>>::type_info()
}
fn compatible(ty: &PgTypeInfo) -> bool {
<sqlx::types::Json<Self> as Type<Postgres>>::compatible(ty)
|| <String as Type<Postgres>>::compatible(ty)
}
}
#[cfg(feature = "postgres")]
impl<'r> Decode<'r, Postgres> for JsonMetadata {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
if <String as Type<Postgres>>::compatible(&value.type_info()) {
let text = <String as Decode<'r, Postgres>>::decode(value)?;
Ok(alibi_core::utils::json::from_slice(text.as_bytes())?)
} else {
<sqlx::types::Json<Self> as Decode<'r, Postgres>>::decode(value).map(|json| json.0)
}
}
}
#[cfg(feature = "postgres")]
impl Encode<'_, Postgres> for JsonMetadata {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
<serde_json::Value as Encode<'_, Postgres>>::encode_by_ref(&self.0, buf)
}
}