Skip to main content

alibi_sqlx/
json_metadata.rs

1//! JSON metadata storage for bundled and application-owned `SQLx` models.
2//!
3//! Use `JsonMetadata` for metadata fields which must retain arbitrary object
4//! keys on reads. Existing `serde_json::Value` fields remain supported but use
5//! `serde_json`'s ordinary decoder. `AuthEntity` calls [`SqlxValue::prepare`]
6//! on set metadata after application hooks and before the store write; manual
7//! models can override `SqlxUserModel::prepare_json_metadata` to choose their
8//! binding policy.
9
10use crate::pool::Engine;
11use crate::value::{ColumnKind, SqlValue, SqlxValue, ValueTypeError};
12use alibi_core::error::AuthResult;
13use sqlx::encode::IsNull;
14use sqlx::error::BoxDynError;
15#[cfg(feature = "postgres")]
16use sqlx::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres};
17#[cfg(feature = "sqlite")]
18use sqlx::sqlite::{Sqlite, SqliteArgumentsBuffer, SqliteTypeInfo, SqliteValueRef};
19use sqlx::{Decode, Encode, Type, ValueRef};
20
21/// JSON storage preserving every application object key.
22///
23/// Convert through `From`; the SQL column remains JSON. SQLite writes bind JavaScript JSON text
24/// directly so every numeric spelling is preserved. Convert to `Value` to edit and construct a
25/// new wrapper before preparation. The prepared value has no mutable access, so its SQLite
26/// binding cannot become stale relative to its public JSON serialization.
27///
28/// ```compile_fail
29/// use alibi_sqlx::JsonMetadata;
30/// let mut metadata = JsonMetadata::from(serde_json::json!({"version": "old"}));
31/// metadata.0["version"] = serde_json::json!("edited");
32/// ```
33#[derive(Clone, Debug, serde::Serialize)]
34#[serde(transparent)]
35pub struct JsonMetadata(serde_json::Value, #[serde(skip)] Option<String>);
36
37impl JsonMetadata {
38    pub(crate) fn for_backend(
39        value: serde_json::Value,
40        backend: Engine,
41    ) -> Result<Self, serde_json::Error> {
42        let text = if backend == Engine::Sqlite {
43            Some(alibi_core::utils::json::to_string(&value)?)
44        } else {
45            None
46        };
47        Ok(Self(value, text))
48    }
49
50    #[cfg(feature = "sqlite")]
51    fn sqlite_text(&self) -> Result<String, serde_json::Error> {
52        match &self.1 {
53            Some(text) => Ok(text.clone()),
54            None => serde_json::to_string(&self.0),
55        }
56    }
57}
58
59impl PartialEq for JsonMetadata {
60    fn eq(&self, other: &Self) -> bool {
61        self.0 == other.0
62    }
63}
64
65impl<'de> serde::Deserialize<'de> for JsonMetadata {
66    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
67        alibi_core::utils::json::deserialize_value(deserializer).map(Self::from)
68    }
69}
70
71impl From<serde_json::Value> for JsonMetadata {
72    fn from(value: serde_json::Value) -> Self {
73        Self(value, None)
74    }
75}
76
77impl From<JsonMetadata> for serde_json::Value {
78    fn from(value: JsonMetadata) -> Self {
79        value.0
80    }
81}
82
83impl std::ops::Deref for JsonMetadata {
84    type Target = serde_json::Value;
85    fn deref(&self) -> &Self::Target {
86        &self.0
87    }
88}
89
90impl SqlxValue for JsonMetadata {
91    const KIND: ColumnKind = ColumnKind::Json;
92    fn null() -> SqlValue {
93        SqlValue::Json(None)
94    }
95    fn into_sql_value(self) -> SqlValue {
96        match self.1 {
97            Some(text) => SqlValue::Text(Some(text)),
98            None => SqlValue::Json(Some(Box::new(self.0))),
99        }
100    }
101    fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
102        match value {
103            SqlValue::Json(Some(value)) => Ok(Self::from(*value)),
104            SqlValue::Text(Some(text)) => {
105                alibi_core::utils::json::from_slice::<serde_json::Value>(text.as_bytes())
106                    .map(Self::from)
107                    .map_err(|_error| ValueTypeError)
108            }
109            _ => Err(ValueTypeError),
110        }
111    }
112    fn prepare(self, engine: Engine) -> AuthResult<Self> {
113        let value = alibi_core::utils::json::to_value(&self.0)?;
114        Ok(Self::for_backend(value, engine)?)
115    }
116}
117
118#[cfg(feature = "sqlite")]
119impl Type<Sqlite> for JsonMetadata {
120    fn type_info() -> SqliteTypeInfo {
121        <sqlx::types::Json<Self> as Type<Sqlite>>::type_info()
122    }
123    fn compatible(ty: &SqliteTypeInfo) -> bool {
124        <sqlx::types::Json<Self> as Type<Sqlite>>::compatible(ty)
125    }
126}
127
128#[cfg(feature = "sqlite")]
129impl<'r> Decode<'r, Sqlite> for JsonMetadata {
130    fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
131        <sqlx::types::Json<Self> as Decode<'r, Sqlite>>::decode(value).map(|json| json.0)
132    }
133}
134
135#[cfg(feature = "sqlite")]
136impl Encode<'_, Sqlite> for JsonMetadata {
137    fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
138        <String as Encode<'_, Sqlite>>::encode(self.sqlite_text()?, buf)
139    }
140}
141
142#[cfg(feature = "postgres")]
143impl Type<Postgres> for JsonMetadata {
144    fn type_info() -> PgTypeInfo {
145        <sqlx::types::Json<Self> as Type<Postgres>>::type_info()
146    }
147    fn compatible(ty: &PgTypeInfo) -> bool {
148        <sqlx::types::Json<Self> as Type<Postgres>>::compatible(ty)
149            || <String as Type<Postgres>>::compatible(ty)
150    }
151}
152
153#[cfg(feature = "postgres")]
154impl<'r> Decode<'r, Postgres> for JsonMetadata {
155    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
156        if <String as Type<Postgres>>::compatible(&value.type_info()) {
157            let text = <String as Decode<'r, Postgres>>::decode(value)?;
158            Ok(alibi_core::utils::json::from_slice(text.as_bytes())?)
159        } else {
160            <sqlx::types::Json<Self> as Decode<'r, Postgres>>::decode(value).map(|json| json.0)
161        }
162    }
163}
164
165#[cfg(feature = "postgres")]
166impl Encode<'_, Postgres> for JsonMetadata {
167    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
168        <serde_json::Value as Encode<'_, Postgres>>::encode_by_ref(&self.0, buf)
169    }
170}