sqlx_transparent_json_decode/
lib.rs1use sqlx::postgres::{PgHasArrayType, PgTypeInfo};
6
7#[cfg(test)]
8mod test;
9
10#[doc(hidden)]
11pub const JSON_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(114);
13#[doc(hidden)]
14pub const JSON_ARRAY_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(199);
16#[doc(hidden)]
17pub const JSONB_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(3802);
19#[doc(hidden)]
20pub const JSONB_ARRAY_OID: sqlx::postgres::types::Oid = sqlx::postgres::types::Oid(3807);
22
23#[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#[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
185pub 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}