Skip to main content

renox_core/db/
json.rs

1use std::ops::{Deref, DerefMut};
2
3use serde::de::DeserializeOwned;
4use serde::{Deserialize, Serialize};
5use sqlx::error::BoxDynError;
6use sqlx::sqlite::{Sqlite, SqliteTypeInfo, SqliteValueRef};
7
8use super::{DbValue, ToDbValue};
9
10/// A model field stored as JSON: `TEXT` on SQLite, `JSONB` (or `JSON`,
11/// `TEXT`) on PostgreSQL. Serializes as the value itself, so templates and
12/// APIs see the list or object.
13///
14/// ```
15/// # use renox::prelude::*;
16/// use renox::db::Json;
17///
18/// #[derive(Model, serde::Serialize, Default)]
19/// #[model(table = "products")]
20/// struct Product {
21///     id: i64,
22///     tags: Json<Vec<String>>, // tags TEXT (SQLite) / tags JSONB (PostgreSQL)
23/// }
24///
25/// let product = Product { tags: Json(vec!["coffee".into()]), ..Default::default() };
26/// assert_eq!(product.tags.len(), 1); // derefs to the Vec
27/// ```
28#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(transparent)]
30pub struct Json<T>(pub T);
31
32impl<T> Deref for Json<T> {
33    type Target = T;
34
35    fn deref(&self) -> &T {
36        &self.0
37    }
38}
39
40impl<T> DerefMut for Json<T> {
41    fn deref_mut(&mut self) -> &mut T {
42        &mut self.0
43    }
44}
45
46impl<T> From<T> for Json<T> {
47    fn from(value: T) -> Self {
48        Json(value)
49    }
50}
51
52impl<T: Serialize> ToDbValue for Json<T> {
53    fn to_db_value(&self) -> DbValue {
54        DbValue::Json(serde_json::to_value(&self.0).unwrap_or(serde_json::Value::Null))
55    }
56}
57
58impl<T> sqlx::Type<Sqlite> for Json<T> {
59    fn type_info() -> SqliteTypeInfo {
60        <String as sqlx::Type<Sqlite>>::type_info()
61    }
62
63    fn compatible(ty: &SqliteTypeInfo) -> bool {
64        <String as sqlx::Type<Sqlite>>::compatible(ty)
65    }
66}
67
68impl<'r, T: DeserializeOwned> sqlx::Decode<'r, Sqlite> for Json<T> {
69    fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
70        let text = <String as sqlx::Decode<Sqlite>>::decode(value)?;
71        Ok(Json(serde_json::from_str(&text)?))
72    }
73}
74
75#[cfg(feature = "postgres")]
76mod postgres {
77    use serde::de::DeserializeOwned;
78    use sqlx::error::BoxDynError;
79    use sqlx::postgres::{PgTypeInfo, PgValueRef, Postgres};
80    use sqlx::{TypeInfo, ValueRef};
81
82    use super::Json;
83
84    impl<T> sqlx::Type<Postgres> for Json<T> {
85        fn type_info() -> PgTypeInfo {
86            PgTypeInfo::with_name("jsonb")
87        }
88
89        fn compatible(ty: &PgTypeInfo) -> bool {
90            matches!(ty.name(), "JSON" | "JSONB")
91                || <String as sqlx::Type<Postgres>>::compatible(ty)
92        }
93    }
94
95    impl<'r, T: DeserializeOwned> sqlx::Decode<'r, Postgres> for Json<T> {
96        fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
97            let is_json = matches!(value.type_info().name(), "JSON" | "JSONB");
98            if is_json {
99                let sqlx::types::Json(inner) =
100                    <sqlx::types::Json<T> as sqlx::Decode<Postgres>>::decode(value)?;
101                Ok(Json(inner))
102            } else {
103                let text = <String as sqlx::Decode<Postgres>>::decode(value)?;
104                Ok(Json(serde_json::from_str(&text)?))
105            }
106        }
107    }
108}