1#![warn(missing_docs)]
48
49#[cfg(all(feature = "sqlx-0_8", feature = "sqlx-0_9"))]
50compile_error!("Enable exactly one SQLx version feature: `sqlx-0_8` or `sqlx-0_9`.");
51#[cfg(not(any(feature = "sqlx-0_8", feature = "sqlx-0_9")))]
52compile_error!("Enable one SQLx version feature: `sqlx-0_8` or `sqlx-0_9`.");
53
54#[cfg(feature = "sqlx-0_8")]
55use sqlx08 as sqlx;
56#[cfg(feature = "sqlx-0_9")]
57use sqlx09 as sqlx;
58
59pub use vld;
60
61#[cfg(feature = "sqlx-0_8")]
62type SqlxArgBuf<'q, DB> = <DB as sqlx::Database>::ArgumentBuffer<'q>;
63#[cfg(feature = "sqlx-0_9")]
64type SqlxArgBuf<'q, DB> = <DB as sqlx::Database>::ArgumentBuffer;
65
66vld_integration_common::__vld_db_error!(
69 VldSqlxError
71);
72
73impl From<VldSqlxError> for sqlx::Error {
76 fn from(e: VldSqlxError) -> Self {
77 sqlx::Error::Encode(Box::new(e))
78 }
79}
80
81vld_integration_common::__vld_db_validated!(VldSqlxError);
82
83vld_integration_common::__vld_db_wrappers! {
84 VldSqlxError;
85 text: []
86 int: []
87 float: []
88 bool: []
89}
90
91pub fn validate_insert<S, T>(value: &T) -> Result<(), VldSqlxError>
116where
117 S: vld::schema::VldParse,
118 T: serde::Serialize,
119{
120 __vld_validate::<S, T>(value).map(|_| ())
121}
122
123pub fn validate_row<S, T>(value: &T) -> Result<(), VldSqlxError>
126where
127 S: vld::schema::VldParse,
128 T: serde::Serialize,
129{
130 validate_insert::<S, T>(value)
131}
132
133pub fn validate_rows<S, T>(rows: &[T]) -> Result<(), (usize, VldSqlxError)>
137where
138 S: vld::schema::VldParse,
139 T: serde::Serialize,
140{
141 for (i, row) in rows.iter().enumerate() {
142 validate_row::<S, T>(row).map_err(|e| (i, e))?;
143 }
144 Ok(())
145}
146
147macro_rules! sqlx_delegate {
153 ($Name:ident, $inner:ty) => {
154 impl<S, DB: sqlx::Database> sqlx::Type<DB> for $Name<S>
155 where
156 $inner: sqlx::Type<DB>,
157 {
158 fn type_info() -> DB::TypeInfo {
159 <$inner as sqlx::Type<DB>>::type_info()
160 }
161 fn compatible(ty: &DB::TypeInfo) -> bool {
162 <$inner as sqlx::Type<DB>>::compatible(ty)
163 }
164 }
165
166 impl<'q, S, DB: sqlx::Database> sqlx::Encode<'q, DB> for $Name<S>
167 where
168 $inner: sqlx::Encode<'q, DB>,
169 {
170 fn encode_by_ref(
171 &self,
172 buf: &mut SqlxArgBuf<'q, DB>,
173 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
174 self.value.encode_by_ref(buf)
175 }
176 }
177
178 impl<'r, S, DB: sqlx::Database> sqlx::Decode<'r, DB> for $Name<S>
179 where
180 $inner: sqlx::Decode<'r, DB>,
181 {
182 fn decode(
183 value: <DB as sqlx::Database>::ValueRef<'r>,
184 ) -> Result<Self, sqlx::error::BoxDynError> {
185 Ok(Self::new_unchecked(
186 <$inner as sqlx::Decode<'r, DB>>::decode(value)?,
187 ))
188 }
189 }
190 };
191}
192
193sqlx_delegate!(VldText, String);
194sqlx_delegate!(VldBool, bool);
195
196impl<S, DB: sqlx::Database> sqlx::Type<DB> for VldFloat<S>
199where
200 f64: sqlx::Type<DB>,
201 f32: sqlx::Type<DB>,
202{
203 fn type_info() -> DB::TypeInfo {
204 <f64 as sqlx::Type<DB>>::type_info()
205 }
206 fn compatible(ty: &DB::TypeInfo) -> bool {
207 <f64 as sqlx::Type<DB>>::compatible(ty) || <f32 as sqlx::Type<DB>>::compatible(ty)
208 }
209}
210
211impl<'q, S, DB: sqlx::Database> sqlx::Encode<'q, DB> for VldFloat<S>
212where
213 f64: sqlx::Encode<'q, DB>,
214{
215 fn encode_by_ref(
216 &self,
217 buf: &mut SqlxArgBuf<'q, DB>,
218 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
219 self.value.encode_by_ref(buf)
220 }
221}
222
223impl<'r, S, DB: sqlx::Database> sqlx::Decode<'r, DB> for VldFloat<S>
224where
225 f64: sqlx::Decode<'r, DB> + sqlx::Type<DB>,
226 f32: sqlx::Decode<'r, DB> + sqlx::Type<DB>,
227{
228 fn decode(
229 value: <DB as sqlx::Database>::ValueRef<'r>,
230 ) -> Result<Self, sqlx::error::BoxDynError> {
231 use sqlx::ValueRef as _;
232 let ty = value.type_info().into_owned();
233 let v = if !<f64 as sqlx::Type<DB>>::compatible(&ty)
234 && <f32 as sqlx::Type<DB>>::compatible(&ty)
235 {
236 let f = <f32 as sqlx::Decode<'r, DB>>::decode(value)?;
237 f.to_string().parse().unwrap_or(f64::from(f))
238 } else {
239 <f64 as sqlx::Decode<'r, DB>>::decode(value)?
240 };
241 Ok(Self::new_unchecked(v))
242 }
243}
244
245impl<S, DB: sqlx::Database> sqlx::Type<DB> for VldInt<S>
248where
249 i64: sqlx::Type<DB>,
250 i32: sqlx::Type<DB>,
251 i16: sqlx::Type<DB>,
252{
253 fn type_info() -> DB::TypeInfo {
254 <i64 as sqlx::Type<DB>>::type_info()
255 }
256 fn compatible(ty: &DB::TypeInfo) -> bool {
257 <i64 as sqlx::Type<DB>>::compatible(ty)
258 || <i32 as sqlx::Type<DB>>::compatible(ty)
259 || <i16 as sqlx::Type<DB>>::compatible(ty)
260 }
261}
262
263impl<'q, S, DB: sqlx::Database> sqlx::Encode<'q, DB> for VldInt<S>
264where
265 i64: sqlx::Encode<'q, DB>,
266{
267 fn encode_by_ref(
268 &self,
269 buf: &mut SqlxArgBuf<'q, DB>,
270 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
271 self.value.encode_by_ref(buf)
272 }
273}
274
275impl<'r, S, DB: sqlx::Database> sqlx::Decode<'r, DB> for VldInt<S>
276where
277 i64: sqlx::Decode<'r, DB> + sqlx::Type<DB>,
278 i32: sqlx::Decode<'r, DB> + sqlx::Type<DB>,
279 i16: sqlx::Decode<'r, DB> + sqlx::Type<DB>,
280{
281 fn decode(
282 value: <DB as sqlx::Database>::ValueRef<'r>,
283 ) -> Result<Self, sqlx::error::BoxDynError> {
284 use sqlx::ValueRef as _;
285 let ty = value.type_info().into_owned();
286 let v = if <i64 as sqlx::Type<DB>>::compatible(&ty) {
287 <i64 as sqlx::Decode<'r, DB>>::decode(value)?
288 } else if <i32 as sqlx::Type<DB>>::compatible(&ty) {
289 <i32 as sqlx::Decode<'r, DB>>::decode(value)?.into()
290 } else if <i16 as sqlx::Type<DB>>::compatible(&ty) {
291 <i16 as sqlx::Decode<'r, DB>>::decode(value)?.into()
292 } else {
293 <i64 as sqlx::Decode<'r, DB>>::decode(value)?
294 };
295 Ok(Self::new_unchecked(v))
296 }
297}
298
299#[cfg(all(test, feature = "postgres"))]
300mod pg_tests {
301 use super::*;
302 use sqlx::Type;
303 use sqlx::postgres::Postgres;
304
305 struct AnySchema;
306
307 #[test]
308 fn vld_float_compatible_with_float4() {
309 for ty in [
310 <f32 as Type<Postgres>>::type_info(),
311 <f64 as Type<Postgres>>::type_info(),
312 ] {
313 assert!(<VldFloat<AnySchema> as Type<Postgres>>::compatible(&ty));
314 }
315 }
316
317 #[test]
318 fn vld_int_compatible_with_int4_and_int2() {
319 for ty in [
320 <i16 as Type<Postgres>>::type_info(),
321 <i32 as Type<Postgres>>::type_info(),
322 <i64 as Type<Postgres>>::type_info(),
323 ] {
324 assert!(<VldInt<AnySchema> as Type<Postgres>>::compatible(&ty));
325 }
326 }
327}
328
329pub mod prelude {
333 pub use crate::{
334 Validated, VldBool, VldFloat, VldInt, VldSqlxError, VldText, validate_insert, validate_row,
335 validate_rows,
336 };
337 pub use vld::prelude::*;
338}