sqlx_transparent_json_decode/
lib.rs

1//! This crate is meant for use with [sqlx](https://github.com/launchbadge/sqlx) and allows you to query JSON
2//! or JSONB fields from PostgreSQL without needing to wrap the types in a `sqlx::types::Json<>` wrapper type.
3//!
4
5use sqlx::postgres::{PgHasArrayType, PgTypeInfo};
6
7#[cfg(test)]
8mod test;
9
10#[doc(hidden)]
11/// This must be exported for the macro to work, but you won't need to use it.
12pub const JSON_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(114);
13#[doc(hidden)]
14/// This must be exported for the macro to work, but you won't need to use it.
15pub const JSON_ARRAY_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(199);
16#[doc(hidden)]
17/// This must be exported for the macro to work, but you won't need to use it.
18pub const JSONB_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(3802);
19#[doc(hidden)]
20/// This must be exported for the macro to work, but you won't need to use it.
21pub const JSONB_ARRAY_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(3807);
22
23/// Generate a Decode implementation for a type that can read it from a PostgreSQL JSON/JSONB field.
24///
25/// ```ignore
26/// use serde::{Deserialize, Serialize};
27/// use sqlx_transparent_json_decode::sqlx_json_decode;
28///
29/// #[derive(Serialize, Deserialize)]
30/// pub struct SomeJsonField {
31///     // Whatever fields match the JSON structure
32///     pub name: String,
33///     pub some_param: Option<String>,
34///     pub count: i32,
35/// }
36///
37/// sqlx_json_decode!(SomeJsonField);
38///
39/// #[derive(sqlx::FromRow)]
40/// pub struct QueryResult {
41///     pub id: i32,
42///     pub name: String,
43///     pub params: SomeJsonField,
44/// }
45/// ```
46///
47/// Normally, you would need to use `Json<SomeJsonField>` as the type for `params` in the above example. This macro allows you to use `SomeJsonField` directly.
48///
49/// ```ignore
50/// let result = sqlx::query_as!(
51///     QueryResult,
52///     r##"SELECT id,
53///         name,
54///         params as "params: SomeJsonField"
55///       FROM some_table"##,
56/// ).fetch_one(&pool).await?;
57/// ```
58#[macro_export]
59macro_rules! sqlx_json_decode {
60    ($type:ty) => {
61        impl<'r> sqlx::Decode<'r, sqlx::Postgres> for $type {
62            fn decode(
63                value: sqlx::postgres::PgValueRef<'r>,
64            ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
65                let buf = $crate::decode_json(value)?;
66                serde_json::from_slice(buf).map_err(Into::into)
67            }
68        }
69
70        impl sqlx::Type<sqlx::Postgres> for $type {
71            fn type_info() -> sqlx::postgres::PgTypeInfo {
72                sqlx::postgres::PgTypeInfo::with_oid($crate::JSONB_OID)
73            }
74
75            fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
76                *ty == sqlx::postgres::PgTypeInfo::with_oid($crate::JSONB_OID)
77                    || *ty == sqlx::postgres::PgTypeInfo::with_oid($crate::JSON_OID)
78            }
79        }
80
81        impl sqlx::postgres::PgHasArrayType for $type {
82            fn array_type_info() -> sqlx::postgres::PgTypeInfo {
83                sqlx::postgres::PgTypeInfo::with_oid($crate::JSONB_ARRAY_OID)
84            }
85
86            fn array_compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
87                *ty == sqlx::postgres::PgTypeInfo::with_oid($crate::JSONB_ARRAY_OID)
88                    || *ty == sqlx::postgres::PgTypeInfo::with_oid($crate::JSON_ARRAY_OID)
89            }
90        }
91    };
92}
93
94/// A wrapper around Box<serde_json::value::RawValue> which can be decoded directly from Postgres.
95#[derive(Debug, Clone)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub struct BoxedRawValue(Box<serde_json::value::RawValue>);
98
99#[cfg(feature = "boxed_raw_value_eq")]
100impl PartialEq for BoxedRawValue {
101    fn eq(&self, other: &Self) -> bool {
102        self.0.get() == other.0.get()
103    }
104}
105
106#[cfg(feature = "boxed_raw_value_eq")]
107impl Eq for BoxedRawValue {}
108
109#[cfg(feature = "schemars")]
110impl schemars::JsonSchema for BoxedRawValue {
111    fn is_referenceable() -> bool {
112        false
113    }
114
115    fn schema_name() -> String {
116        serde_json::value::RawValue::schema_name()
117    }
118
119    fn schema_id() -> std::borrow::Cow<'static, str> {
120        serde_json::value::RawValue::schema_id()
121    }
122
123    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
124        serde_json::value::RawValue::json_schema(gen)
125    }
126}
127
128impl std::ops::Deref for BoxedRawValue {
129    type Target = serde_json::value::RawValue;
130    fn deref(&self) -> &Self::Target {
131        &self.0
132    }
133}
134
135impl From<BoxedRawValue> for Box<serde_json::value::RawValue> {
136    fn from(raw_value: BoxedRawValue) -> Self {
137        raw_value.0
138    }
139}
140
141impl<'r> sqlx::Decode<'r, sqlx::Postgres> for BoxedRawValue {
142    fn decode(
143        value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
144    ) -> Result<Self, sqlx::error::BoxDynError> {
145        let buf = decode_json(value)?;
146        let string = std::str::from_utf8(buf)?;
147        let raw_value = serde_json::value::RawValue::from_string(string.to_owned())?;
148        Ok(BoxedRawValue(raw_value))
149    }
150}
151
152impl PgHasArrayType for BoxedRawValue {
153    fn array_type_info() -> PgTypeInfo {
154        serde_json::value::RawValue::array_type_info()
155    }
156
157    fn array_compatible(ty: &PgTypeInfo) -> bool {
158        serde_json::value::RawValue::array_compatible(ty)
159    }
160}
161
162impl<'r> sqlx::Encode<'r, sqlx::Postgres> for BoxedRawValue {
163    fn encode_by_ref(
164        &self,
165        out: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'r>,
166    ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
167        let j = sqlx::types::Json(&self.0);
168        <sqlx::types::Json<&Box<sqlx::types::JsonRawValue>> as sqlx::Encode<'_, sqlx::Postgres>>::encode_by_ref(
169            &j, out,
170        )
171    }
172}
173
174impl sqlx::Type<sqlx::Postgres> for BoxedRawValue {
175    fn type_info() -> sqlx::postgres::PgTypeInfo {
176        sqlx::postgres::PgTypeInfo::with_oid(JSONB_OID)
177    }
178
179    fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
180        *ty == sqlx::postgres::PgTypeInfo::with_oid(JSONB_OID)
181            || *ty == sqlx::postgres::PgTypeInfo::with_oid(JSON_OID)
182    }
183}
184
185/// Extract a byte slice from a Postgres JSON or JSONB value. You shouldn't need to use this directly.
186pub fn decode_json(
187    value: <sqlx::Postgres as sqlx::Database>::ValueRef<'_>,
188) -> Result<&'_ [u8], sqlx::error::BoxDynError> {
189    use sqlx::ValueRef;
190    let is_jsonb = value.type_info().as_ref() == &sqlx::postgres::PgTypeInfo::with_oid(JSONB_OID);
191    let mut buf = <&[u8] as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
192
193    if is_jsonb {
194        assert_eq!(
195            buf[0], 1,
196            "unsupported JSONB format version {}; please open an issue",
197            buf[0]
198        );
199
200        buf = &buf[1..];
201    }
202
203    Ok(buf)
204}