1extern crate alloc;
25
26use alloc::string::{String, ToString};
27use alloc::vec::Vec;
28
29use facet_core::{Facet, Shape, StructKind, Type, UserType};
30use facet_reflect::{Partial, ReflectError};
31use tokio_postgres::Row;
32
33#[derive(Debug)]
35pub enum Error {
36 MissingColumn {
38 column: String,
40 },
41 TypeMismatch {
43 column: String,
45 expected: &'static Shape,
47 source: tokio_postgres::Error,
49 },
50 Reflect(ReflectError),
52 NotAStruct {
54 shape: &'static Shape,
56 },
57 UnsupportedType {
59 field: String,
61 shape: &'static Shape,
63 },
64}
65
66impl core::fmt::Display for Error {
67 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68 match self {
69 Error::MissingColumn { column } => write!(f, "missing column: {column}"),
70 Error::TypeMismatch {
71 column, expected, ..
72 } => {
73 write!(
74 f,
75 "type mismatch for column '{column}': expected {expected}"
76 )
77 }
78 Error::Reflect(e) => write!(f, "reflection error: {e}"),
79 Error::NotAStruct { shape } => {
80 write!(f, "cannot deserialize row into non-struct type: {shape}")
81 }
82 Error::UnsupportedType { field, shape } => {
83 write!(f, "unsupported type for field '{field}': {shape}")
84 }
85 }
86 }
87}
88
89impl std::error::Error for Error {
90 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
91 match self {
92 Error::TypeMismatch { source, .. } => Some(source),
93 Error::Reflect(e) => Some(e),
94 _ => None,
95 }
96 }
97}
98
99impl From<ReflectError> for Error {
100 fn from(e: ReflectError) -> Self {
101 Error::Reflect(e)
102 }
103}
104
105pub type Result<T> = core::result::Result<T, Error>;
107
108pub fn from_row<'facet, T: Facet<'facet>>(row: &Row) -> Result<T> {
130 let partial = Partial::alloc::<T>()?;
131 let partial = deserialize_row_into(row, partial, T::SHAPE)?;
132 let heap_value = partial.build()?;
133 Ok(heap_value.materialize()?)
134}
135
136fn deserialize_row_into<'p>(
138 row: &Row,
139 partial: Partial<'p>,
140 shape: &'static Shape,
141) -> Result<Partial<'p>> {
142 let struct_def = match &shape.ty {
143 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => s,
144 _ => {
145 return Err(Error::NotAStruct { shape });
146 }
147 };
148
149 let mut partial = partial;
150 let num_fields = struct_def.fields.len();
151 let mut fields_set = alloc::vec![false; num_fields];
152
153 for (idx, field) in struct_def.fields.iter().enumerate() {
154 let column_name = field.rename.unwrap_or(field.name);
155
156 let column_idx = match row.columns().iter().position(|c| c.name() == column_name) {
158 Some(idx) => idx,
159 None => {
160 partial =
162 partial
163 .set_nth_field_to_default(idx)
164 .map_err(|_| Error::MissingColumn {
165 column: column_name.to_string(),
166 })?;
167 fields_set[idx] = true;
168 continue;
169 }
170 };
171
172 partial = partial.begin_field(field.name)?;
173 partial = deserialize_column(row, column_idx, column_name, partial, field.shape())?;
174 partial = partial.end()?;
175 fields_set[idx] = true;
176 }
177
178 Ok(partial)
179}
180
181fn deserialize_column<'p>(
183 row: &Row,
184 column_idx: usize,
185 column_name: &str,
186 partial: Partial<'p>,
187 shape: &'static Shape,
188) -> Result<Partial<'p>> {
189 use facet_core::{Def, NumericType, PrimitiveType};
190
191 let mut partial = partial;
192
193 if let Def::Option(_) = &shape.def {
195 return deserialize_option_column(row, column_idx, column_name, partial, shape);
196 }
197
198 match &shape.ty {
200 Type::Primitive(PrimitiveType::Numeric(NumericType::Integer { signed: true })) => {
202 match shape.type_identifier {
203 "i8" => {
204 let val: i8 = get_column(row, column_idx, column_name, shape)?;
205 partial = partial.set(val)?;
206 }
207 "i16" => {
208 let val: i16 = get_column(row, column_idx, column_name, shape)?;
209 partial = partial.set(val)?;
210 }
211 "i32" => {
212 let val: i32 = get_column(row, column_idx, column_name, shape)?;
213 partial = partial.set(val)?;
214 }
215 "i64" => {
216 let val: i64 = get_column(row, column_idx, column_name, shape)?;
217 partial = partial.set(val)?;
218 }
219 _ => {
220 return Err(Error::UnsupportedType {
221 field: column_name.to_string(),
222 shape,
223 });
224 }
225 }
226 }
227
228 Type::Primitive(PrimitiveType::Numeric(NumericType::Integer { signed: false })) => {
230 match shape.type_identifier {
232 "u8" => {
233 let val: i16 = get_column(row, column_idx, column_name, shape)?;
234 partial = partial.set(val as u8)?;
235 }
236 "u16" => {
237 let val: i32 = get_column(row, column_idx, column_name, shape)?;
238 partial = partial.set(val as u16)?;
239 }
240 "u32" => {
241 let val: i64 = get_column(row, column_idx, column_name, shape)?;
242 partial = partial.set(val as u32)?;
243 }
244 "u64" => {
245 let val: i64 = get_column(row, column_idx, column_name, shape)?;
247 partial = partial.set(val as u64)?;
248 }
249 _ => {
250 return Err(Error::UnsupportedType {
251 field: column_name.to_string(),
252 shape,
253 });
254 }
255 }
256 }
257
258 Type::Primitive(PrimitiveType::Numeric(NumericType::Float)) => {
260 match shape.type_identifier {
261 "f32" => {
262 let val: f32 = get_column(row, column_idx, column_name, shape)?;
263 partial = partial.set(val)?;
264 }
265 "f64" => {
266 let val: f64 = get_column(row, column_idx, column_name, shape)?;
267 partial = partial.set(val)?;
268 }
269 _ => {
270 return Err(Error::UnsupportedType {
271 field: column_name.to_string(),
272 shape,
273 });
274 }
275 }
276 }
277
278 Type::Primitive(PrimitiveType::Boolean) => {
280 let val: bool = get_column(row, column_idx, column_name, shape)?;
281 partial = partial.set(val)?;
282 }
283
284 Type::Primitive(PrimitiveType::Textual(_)) | Type::User(_)
286 if shape.type_identifier == "String" =>
287 {
288 let val: String = get_column(row, column_idx, column_name, shape)?;
289 partial = partial.set(val)?;
290 }
291
292 _ if matches!(&shape.def, Def::List(_))
294 && shape
295 .inner
296 .is_some_and(|inner| inner.type_identifier == "u8") =>
297 {
298 let val: Vec<u8> = get_column(row, column_idx, column_name, shape)?;
299 partial = partial.set(val)?;
300 }
301
302 #[cfg(feature = "rust_decimal")]
304 _ if shape.type_identifier == "Decimal" => {
305 let val: rust_decimal::Decimal = get_column(row, column_idx, column_name, shape)?;
306 partial = partial.set(val)?;
307 }
308
309 #[cfg(feature = "jiff02")]
311 _ if shape.type_identifier == "Timestamp" && shape.module_path == Some("jiff") => {
312 let val: jiff::Timestamp = get_column(row, column_idx, column_name, shape)?;
313 partial = partial.set(val)?;
314 }
315
316 #[cfg(feature = "jiff02")]
318 _ if shape.type_identifier == "DateTime" && shape.module_path == Some("jiff") => {
319 let val: jiff::civil::DateTime = get_column(row, column_idx, column_name, shape)?;
320 partial = partial.set(val)?;
321 }
322
323 _ => {
325 if shape.vtable.has_parse() {
326 let val: String = get_column(row, column_idx, column_name, shape)?;
328 partial = partial.parse_from_str(&val)?;
329 } else {
330 return Err(Error::UnsupportedType {
331 field: column_name.to_string(),
332 shape,
333 });
334 }
335 }
336 }
337
338 Ok(partial)
339}
340
341fn deserialize_option_column<'p>(
343 row: &Row,
344 column_idx: usize,
345 column_name: &str,
346 partial: Partial<'p>,
347 shape: &'static Shape,
348) -> Result<Partial<'p>> {
349 use facet_core::{NumericType, PrimitiveType};
350
351 let inner_shape = shape.inner.expect("Option must have inner shape");
352 let mut partial = partial;
353
354 macro_rules! try_option {
357 ($t:ty) => {{
358 let val: Option<$t> = get_column(row, column_idx, column_name, shape)?;
359 match val {
360 Some(v) => {
361 partial = partial.begin_some()?;
362 partial = partial.set(v)?;
363 partial = partial.end()?;
364 }
365 None => {
366 partial = partial.set_default()?;
367 }
368 }
369 return Ok(partial);
370 }};
371 }
372
373 match &inner_shape.ty {
375 Type::Primitive(PrimitiveType::Numeric(NumericType::Integer { signed: true })) => {
376 match inner_shape.type_identifier {
377 "i8" => try_option!(i8),
378 "i16" => try_option!(i16),
379 "i32" => try_option!(i32),
380 "i64" => try_option!(i64),
381 _ => {}
382 }
383 }
384 Type::Primitive(PrimitiveType::Numeric(NumericType::Integer { signed: false })) => {
385 match inner_shape.type_identifier {
387 "u8" => {
388 let val: Option<i16> = get_column(row, column_idx, column_name, shape)?;
389 match val {
390 Some(v) => {
391 partial = partial.begin_some()?;
392 partial = partial.set(v as u8)?;
393 partial = partial.end()?;
394 }
395 None => {
396 partial = partial.set_default()?;
397 }
398 }
399 return Ok(partial);
400 }
401 "u16" => {
402 let val: Option<i32> = get_column(row, column_idx, column_name, shape)?;
403 match val {
404 Some(v) => {
405 partial = partial.begin_some()?;
406 partial = partial.set(v as u16)?;
407 partial = partial.end()?;
408 }
409 None => {
410 partial = partial.set_default()?;
411 }
412 }
413 return Ok(partial);
414 }
415 "u32" => {
416 let val: Option<i64> = get_column(row, column_idx, column_name, shape)?;
417 match val {
418 Some(v) => {
419 partial = partial.begin_some()?;
420 partial = partial.set(v as u32)?;
421 partial = partial.end()?;
422 }
423 None => {
424 partial = partial.set_default()?;
425 }
426 }
427 return Ok(partial);
428 }
429 "u64" => {
430 let val: Option<i64> = get_column(row, column_idx, column_name, shape)?;
431 match val {
432 Some(v) => {
433 partial = partial.begin_some()?;
434 partial = partial.set(v as u64)?;
435 partial = partial.end()?;
436 }
437 None => {
438 partial = partial.set_default()?;
439 }
440 }
441 return Ok(partial);
442 }
443 _ => {}
444 }
445 }
446 Type::Primitive(PrimitiveType::Numeric(NumericType::Float)) => {
447 match inner_shape.type_identifier {
448 "f32" => try_option!(f32),
449 "f64" => try_option!(f64),
450 _ => {}
451 }
452 }
453 Type::Primitive(PrimitiveType::Boolean) => try_option!(bool),
454 _ if inner_shape.type_identifier == "String" => try_option!(String),
455 #[cfg(feature = "rust_decimal")]
456 _ if inner_shape.type_identifier == "Decimal" => try_option!(rust_decimal::Decimal),
457 #[cfg(feature = "jiff02")]
458 _ if inner_shape.type_identifier == "Timestamp"
459 && inner_shape.module_path == Some("jiff") =>
460 {
461 try_option!(jiff::Timestamp)
462 }
463 #[cfg(feature = "jiff02")]
464 _ if inner_shape.type_identifier == "DateTime"
465 && inner_shape.module_path == Some("jiff") =>
466 {
467 try_option!(jiff::civil::DateTime)
468 }
469 _ => {}
470 }
471
472 if inner_shape.vtable.has_parse() {
474 let val: Option<String> = get_column(row, column_idx, column_name, shape)?;
475 match val {
476 Some(s) => {
477 partial = partial.begin_some()?;
478 partial = partial.parse_from_str(&s)?;
479 partial = partial.end()?;
480 }
481 None => {
482 partial = partial.set_default()?;
483 }
484 }
485 return Ok(partial);
486 }
487
488 Err(Error::UnsupportedType {
489 field: column_name.to_string(),
490 shape: inner_shape,
491 })
492}
493
494fn get_column<'a, T>(row: &'a Row, idx: usize, name: &str, shape: &'static Shape) -> Result<T>
496where
497 T: postgres_types::FromSql<'a>,
498{
499 row.try_get::<_, T>(idx).map_err(|e| Error::TypeMismatch {
500 column: name.to_string(),
501 expected: shape,
502 source: e,
503 })
504}