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};
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            SqlValue::Bool(_)
110            | SqlValue::Int(_)
111            | SqlValue::BigInt(_)
112            | SqlValue::Float(_)
113            | SqlValue::Double(_)
114            | SqlValue::Text(None)
115            | SqlValue::BpChar(_)
116            | SqlValue::Bytes(_)
117            | SqlValue::Json(None)
118            | SqlValue::Timestamp(_)
119            | SqlValue::NaiveTimestamp(_)
120            | SqlValue::Uuid(_) => Err(ValueTypeError),
121        }
122    }
123    fn prepare(self, engine: Engine) -> AuthResult<Self> {
124        let value = alibi_core::utils::json::to_value(&self.0)?;
125        Ok(Self::for_backend(value, engine)?)
126    }
127}
128
129#[cfg(feature = "sqlite")]
130impl Type<Sqlite> for JsonMetadata {
131    fn type_info() -> SqliteTypeInfo {
132        <sqlx::types::Json<Self> as Type<Sqlite>>::type_info()
133    }
134    fn compatible(ty: &SqliteTypeInfo) -> bool {
135        <sqlx::types::Json<Self> as Type<Sqlite>>::compatible(ty)
136    }
137}
138
139#[cfg(feature = "sqlite")]
140impl<'r> Decode<'r, Sqlite> for JsonMetadata {
141    fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
142        <sqlx::types::Json<Self> as Decode<'r, Sqlite>>::decode(value).map(|json| json.0)
143    }
144}
145
146#[cfg(feature = "sqlite")]
147impl Encode<'_, Sqlite> for JsonMetadata {
148    fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
149        <String as Encode<'_, Sqlite>>::encode(self.sqlite_text()?, buf)
150    }
151}
152
153#[cfg(feature = "postgres")]
154impl Type<Postgres> for JsonMetadata {
155    fn type_info() -> PgTypeInfo {
156        <sqlx::types::Json<Self> as Type<Postgres>>::type_info()
157    }
158    fn compatible(ty: &PgTypeInfo) -> bool {
159        <sqlx::types::Json<Self> as Type<Postgres>>::compatible(ty)
160    }
161}
162
163#[cfg(feature = "postgres")]
164impl<'r> Decode<'r, Postgres> for JsonMetadata {
165    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
166        <sqlx::types::Json<Self> as Decode<'r, Postgres>>::decode(value).map(|json| json.0)
167    }
168}
169
170#[cfg(feature = "postgres")]
171impl Encode<'_, Postgres> for JsonMetadata {
172    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
173        <serde_json::Value as Encode<'_, Postgres>>::encode_by_ref(&self.0, buf)
174    }
175}