Skip to main content

agrum/
structure.rs

1use std::error::Error;
2use std::fmt::Display;
3
4use tokio_postgres::{Row, error::Error as PgError};
5
6use crate::Projection;
7
8/// SQL field structure.
9#[derive(Debug, PartialEq, Eq, Clone)]
10pub struct StructureField {
11    /// Name of the field.
12    name: String,
13
14    /// SQL type of the field.
15    sql_type: String,
16}
17
18impl StructureField {
19    /// Create a new structure field.
20    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    /// Dump the structure field as a tuple of name and SQL type.
28    pub fn dump(&self) -> (&str, &str) {
29        (&self.name, &self.sql_type)
30    }
31}
32/// Structure of a SQL tuple.
33#[derive(Debug, Clone, Default)]
34pub struct Structure {
35    fields: Vec<StructureField>,
36}
37
38impl Structure {
39    /// Create a new instance of Structure from a slice of tuples.
40    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    /// Set a field in the structure.
51    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    /// Get the fields of the structure.
62    pub fn get_fields(&self) -> &Vec<StructureField> {
63        &self.fields
64    }
65
66    /// Get the names of the fields in the structure.
67    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
74/// A trait to mark types that are structured.
75/// A structured type is a type that has a structure.
76/// The structure is a list of fields with their names and SQL types.
77pub trait Structured {
78    /// Get the structure of the type.
79    fn get_structure() -> Structure;
80}
81
82/// Error raised during entity hydration process.
83#[derive(Debug)]
84pub enum HydrationError {
85    /// Data could not be parsed or cast in the expected structure.
86    InvalidData(String),
87
88    /// Error while fetching data from the database.
89    FieldFetchFailed {
90        /// Error while fetching the data.
91        error: PgError,
92        /// Index of the field that failed to fetch.
93        field_index: usize,
94    },
95
96    /// Error while fetching the Row from the database.
97    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
115/// A trait to mark types that are SQL entities.
116/// An SQL entity is a type that has a structure, a projection and a hydration function.
117pub trait SqlEntity: Structured + Sized {
118    /// Get the projection of the entity.
119    fn get_projection() -> Projection<Self>;
120
121    /// Hydrate the entity from a row.
122    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}