1use std::error::Error;
2use std::fmt::Display;
3
4use tokio_postgres::{Row, error::Error as PgError};
5
6use crate::Projection;
7
8#[derive(Debug, PartialEq, Eq, Clone)]
10pub struct StructureField {
11 name: String,
13
14 sql_type: String,
16}
17
18impl StructureField {
19 pub fn new(name: &str, sql_type: &str) -> Self {
21 Self {
22 name: name.to_string(),
23 sql_type: sql_type.to_string(),
24 }
25 }
26
27 pub fn dump(&self) -> (&str, &str) {
29 (&self.name, &self.sql_type)
30 }
31}
32#[derive(Debug, Clone, Default)]
34pub struct Structure {
35 fields: Vec<StructureField>,
36}
37
38impl Structure {
39 pub fn new(field_definitions: &[(&str, &str)]) -> Self {
41 let mut fields: Vec<StructureField> = Vec::new();
42
43 for (name, sql_type) in field_definitions {
44 fields.push(StructureField::new(name, sql_type));
45 }
46
47 Self { fields }
48 }
49
50 pub fn set_field(&mut self, name: &str, sql_type: &str) -> &mut Self {
52 let name = name.to_string();
53 let sql_type = sql_type.to_string();
54
55 let definition = StructureField { name, sql_type };
56 self.fields.push(definition);
57
58 self
59 }
60
61 pub fn get_fields(&self) -> &Vec<StructureField> {
63 &self.fields
64 }
65
66 pub fn get_names(&self) -> Vec<&str> {
68 let names: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
69
70 names
71 }
72}
73
74pub trait Structured {
78 fn get_structure() -> Structure;
80}
81
82#[derive(Debug)]
84pub enum HydrationError {
85 InvalidData(String),
87
88 FieldFetchFailed {
90 error: PgError,
92 field_index: usize,
94 },
95
96 RowFetchFailed(PgError),
98}
99
100impl Display for HydrationError {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 Self::InvalidData(msg) => write!(f, "Invalid data error: «{msg}»"),
104 Self::FieldFetchFailed { error, field_index } => write!(
105 f,
106 "Fail to fetch data for field index {field_index}, message: «{error}»."
107 ),
108 Self::RowFetchFailed(e) => write!(f, "Fail to fetch the row, message «{e}»."),
109 }
110 }
111}
112
113impl Error for HydrationError {}
114
115pub trait SqlEntity: Structured + Sized {
118 fn get_projection() -> Projection<Self>;
120
121 fn hydrate(row: &Row) -> Result<Self, HydrationError>;
123}
124
125#[cfg(test)]
126mod tests {
127
128 use super::*;
129
130 fn get_structure() -> Structure {
131 Structure::new(&[("a_field", "a_type"), ("another_field", "another_type")])
132 }
133
134 #[test]
135 fn use_structure() {
136 let structure = get_structure();
137
138 assert_eq!(
139 &[
140 StructureField {
141 name: "a_field".to_string(),
142 sql_type: "a_type".to_string()
143 },
144 StructureField {
145 name: "another_field".to_string(),
146 sql_type: "another_type".to_string()
147 }
148 ]
149 .to_vec(),
150 structure.get_fields()
151 );
152 }
153
154 #[test]
155 fn get_names() {
156 let structure = get_structure();
157 assert_eq!(vec!["a_field", "another_field"], structure.get_names());
158 }
159}