1#![allow(clippy::expect_used)]
5
6use crate::error::TypeError;
7use crate::value::SqlValue;
8
9pub trait FromSql: Sized {
14 fn from_sql(value: &SqlValue) -> Result<Self, TypeError>;
16
17 fn from_sql_nullable(value: &SqlValue) -> Result<Option<Self>, TypeError> {
21 if value.is_null() {
22 Ok(None)
23 } else {
24 Self::from_sql(value).map(Some)
25 }
26 }
27}
28
29impl FromSql for bool {
30 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
31 match value {
32 SqlValue::Bool(v) => Ok(*v),
33 SqlValue::TinyInt(v) => Ok(*v != 0),
34 SqlValue::SmallInt(v) => Ok(*v != 0),
35 SqlValue::Int(v) => Ok(*v != 0),
36 SqlValue::Null => Err(TypeError::UnexpectedNull),
37 _ => Err(TypeError::TypeMismatch {
38 expected: "bool",
39 actual: value.type_name().to_string(),
40 }),
41 }
42 }
43}
44
45impl FromSql for u8 {
46 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
47 match value {
48 SqlValue::TinyInt(v) => Ok(*v),
49 SqlValue::Null => Err(TypeError::UnexpectedNull),
50 _ => Err(TypeError::TypeMismatch {
51 expected: "u8",
52 actual: value.type_name().to_string(),
53 }),
54 }
55 }
56}
57
58impl FromSql for i16 {
59 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
60 match value {
61 SqlValue::SmallInt(v) => Ok(*v),
62 SqlValue::TinyInt(v) => Ok(*v as i16),
63 SqlValue::Null => Err(TypeError::UnexpectedNull),
64 _ => Err(TypeError::TypeMismatch {
65 expected: "i16",
66 actual: value.type_name().to_string(),
67 }),
68 }
69 }
70}
71
72impl FromSql for i32 {
73 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
74 match value {
75 SqlValue::Int(v) => Ok(*v),
76 SqlValue::SmallInt(v) => Ok(*v as i32),
77 SqlValue::TinyInt(v) => Ok(*v as i32),
78 SqlValue::Null => Err(TypeError::UnexpectedNull),
79 _ => Err(TypeError::TypeMismatch {
80 expected: "i32",
81 actual: value.type_name().to_string(),
82 }),
83 }
84 }
85}
86
87impl FromSql for i64 {
88 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
89 match value {
90 SqlValue::BigInt(v) => Ok(*v),
91 SqlValue::Int(v) => Ok(*v as i64),
92 SqlValue::SmallInt(v) => Ok(*v as i64),
93 SqlValue::TinyInt(v) => Ok(*v as i64),
94 SqlValue::Null => Err(TypeError::UnexpectedNull),
95 _ => Err(TypeError::TypeMismatch {
96 expected: "i64",
97 actual: value.type_name().to_string(),
98 }),
99 }
100 }
101}
102
103impl FromSql for f32 {
104 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
105 match value {
106 SqlValue::Float(v) => Ok(*v),
107 SqlValue::Null => Err(TypeError::UnexpectedNull),
108 _ => Err(TypeError::TypeMismatch {
109 expected: "f32",
110 actual: value.type_name().to_string(),
111 }),
112 }
113 }
114}
115
116impl FromSql for f64 {
117 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
118 match value {
119 SqlValue::Double(v) => Ok(*v),
120 SqlValue::Float(v) => Ok(*v as f64),
121 SqlValue::Null => Err(TypeError::UnexpectedNull),
122 _ => Err(TypeError::TypeMismatch {
123 expected: "f64",
124 actual: value.type_name().to_string(),
125 }),
126 }
127 }
128}
129
130impl FromSql for String {
131 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
132 match value {
133 SqlValue::String(v) => Ok(v.clone()),
134 SqlValue::Xml(v) => Ok(v.clone()),
135 SqlValue::Null => Err(TypeError::UnexpectedNull),
136 _ => Err(TypeError::TypeMismatch {
137 expected: "String",
138 actual: value.type_name().to_string(),
139 }),
140 }
141 }
142}
143
144impl FromSql for Vec<u8> {
145 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
146 match value {
147 SqlValue::Binary(v) => Ok(v.to_vec()),
148 SqlValue::Null => Err(TypeError::UnexpectedNull),
149 _ => Err(TypeError::TypeMismatch {
150 expected: "Vec<u8>",
151 actual: value.type_name().to_string(),
152 }),
153 }
154 }
155}
156
157impl<T: FromSql> FromSql for Option<T> {
158 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
159 T::from_sql_nullable(value)
160 }
161}
162
163#[cfg(feature = "uuid")]
164impl FromSql for uuid::Uuid {
165 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
166 match value {
167 SqlValue::Uuid(v) => Ok(*v),
168 SqlValue::Binary(b) if b.len() == 16 => {
169 let bytes: [u8; 16] = b[..]
170 .try_into()
171 .map_err(|_| TypeError::InvalidUuid("invalid UUID length".to_string()))?;
172 Ok(uuid::Uuid::from_bytes(bytes))
173 }
174 SqlValue::String(s) => s
175 .parse()
176 .map_err(|e| TypeError::InvalidUuid(format!("{e}"))),
177 SqlValue::Null => Err(TypeError::UnexpectedNull),
178 _ => Err(TypeError::TypeMismatch {
179 expected: "Uuid",
180 actual: value.type_name().to_string(),
181 }),
182 }
183 }
184}
185
186#[cfg(feature = "decimal")]
187impl FromSql for rust_decimal::Decimal {
188 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
189 match value {
190 SqlValue::Decimal(v) => Ok(*v),
191 SqlValue::Int(v) => Ok(rust_decimal::Decimal::from(*v)),
192 SqlValue::BigInt(v) => Ok(rust_decimal::Decimal::from(*v)),
193 SqlValue::String(s) => s
194 .parse()
195 .map_err(|e| TypeError::InvalidDecimal(format!("{e}"))),
196 SqlValue::Null => Err(TypeError::UnexpectedNull),
197 _ => Err(TypeError::TypeMismatch {
198 expected: "Decimal",
199 actual: value.type_name().to_string(),
200 }),
201 }
202 }
203}
204
205#[cfg(feature = "chrono")]
206impl FromSql for chrono::NaiveDate {
207 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
208 match value {
209 SqlValue::Date(v) => Ok(*v),
210 SqlValue::DateTime(v) => Ok(v.date()),
211 SqlValue::Null => Err(TypeError::UnexpectedNull),
212 _ => Err(TypeError::TypeMismatch {
213 expected: "NaiveDate",
214 actual: value.type_name().to_string(),
215 }),
216 }
217 }
218}
219
220#[cfg(feature = "chrono")]
221impl FromSql for chrono::NaiveTime {
222 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
223 match value {
224 SqlValue::Time(v) => Ok(*v),
225 SqlValue::DateTime(v) => Ok(v.time()),
226 SqlValue::Null => Err(TypeError::UnexpectedNull),
227 _ => Err(TypeError::TypeMismatch {
228 expected: "NaiveTime",
229 actual: value.type_name().to_string(),
230 }),
231 }
232 }
233}
234
235#[cfg(feature = "chrono")]
236impl FromSql for chrono::NaiveDateTime {
237 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
238 match value {
239 SqlValue::DateTime(v) => Ok(*v),
240 SqlValue::DateTimeOffset(v) => Ok(v.naive_utc()),
241 SqlValue::Null => Err(TypeError::UnexpectedNull),
242 _ => Err(TypeError::TypeMismatch {
243 expected: "NaiveDateTime",
244 actual: value.type_name().to_string(),
245 }),
246 }
247 }
248}
249
250#[cfg(feature = "chrono")]
251impl FromSql for chrono::DateTime<chrono::FixedOffset> {
252 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
253 match value {
254 SqlValue::DateTimeOffset(v) => Ok(*v),
255 SqlValue::Null => Err(TypeError::UnexpectedNull),
256 _ => Err(TypeError::TypeMismatch {
257 expected: "DateTime<FixedOffset>",
258 actual: value.type_name().to_string(),
259 }),
260 }
261 }
262}
263
264#[cfg(feature = "chrono")]
265impl FromSql for chrono::DateTime<chrono::Utc> {
266 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
267 match value {
268 SqlValue::DateTimeOffset(v) => Ok(v.to_utc()),
269 SqlValue::DateTime(v) => {
270 Ok(chrono::DateTime::from_naive_utc_and_offset(*v, chrono::Utc))
271 }
272 SqlValue::Null => Err(TypeError::UnexpectedNull),
273 _ => Err(TypeError::TypeMismatch {
274 expected: "DateTime<Utc>",
275 actual: value.type_name().to_string(),
276 }),
277 }
278 }
279}
280
281#[cfg(feature = "json")]
282impl FromSql for serde_json::Value {
283 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
284 match value {
285 SqlValue::Json(v) => Ok(v.clone()),
286 SqlValue::String(s) => serde_json::from_str(s).map_err(|e| TypeError::TypeMismatch {
287 expected: "JSON",
288 actual: format!("invalid JSON: {e}"),
289 }),
290 SqlValue::Null => Ok(serde_json::Value::Null),
291 _ => Err(TypeError::TypeMismatch {
292 expected: "JSON",
293 actual: value.type_name().to_string(),
294 }),
295 }
296 }
297}
298
299#[cfg(test)]
300#[allow(clippy::unwrap_used)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_from_sql_i32() {
306 let value = SqlValue::Int(42);
307 assert_eq!(i32::from_sql(&value).unwrap(), 42);
308 }
309
310 #[test]
311 fn test_from_sql_string() {
312 let value = SqlValue::String("hello".to_string());
313 assert_eq!(String::from_sql(&value).unwrap(), "hello");
314 }
315
316 #[test]
317 fn test_from_sql_null() {
318 let value = SqlValue::Null;
319 assert!(i32::from_sql(&value).is_err());
320 }
321
322 #[test]
323 fn test_from_sql_option() {
324 let value = SqlValue::Int(42);
325 assert_eq!(Option::<i32>::from_sql(&value).unwrap(), Some(42));
326
327 let null = SqlValue::Null;
328 assert_eq!(Option::<i32>::from_sql(&null).unwrap(), None);
329 }
330}