mssql_client/from_row.rs
1//! FromRow trait for automatic row-to-struct mapping.
2//!
3//! This module provides the `FromRow` trait which enables automatic conversion
4//! from database rows to Rust structs.
5//!
6//! ## Derive Macro
7//!
8//! The recommended way to implement `FromRow` is via the derive macro from
9//! `mssql-derive`:
10//!
11//! ```rust,ignore
12//! use mssql_derive::FromRow;
13//!
14//! #[derive(FromRow)]
15//! struct User {
16//! id: i32,
17//! #[mssql(rename = "user_name")]
18//! name: String,
19//! email: Option<String>,
20//! }
21//! ```
22//!
23//! ## Supported Attributes
24//!
25//! - `#[mssql(rename = "column_name")]` - Map field to a different column name
26//! - `#[mssql(skip)]` - Skip field, use Default value
27//! - `#[mssql(default)]` - Use Default if column not found
28//! - `#[mssql(flatten)]` - Flatten nested FromRow structs
29
30use crate::error::Error;
31use crate::row::Row;
32
33/// Trait for types that can be constructed from a database row.
34///
35/// This trait is typically implemented via the `#[derive(FromRow)]` macro,
36/// but can also be implemented manually for custom mapping logic.
37///
38/// # Example
39///
40/// ```rust,ignore
41/// use mssql_client::{FromRow, Row, Error};
42///
43/// struct User {
44/// id: i32,
45/// name: String,
46/// }
47///
48/// impl FromRow for User {
49/// fn from_row(row: &Row) -> Result<Self, Error> {
50/// Ok(Self {
51/// id: row.get_by_name("id")?,
52/// name: row.get_by_name("name")?,
53/// })
54/// }
55/// }
56/// ```
57pub trait FromRow: Sized {
58 /// Construct an instance of this type from a database row.
59 ///
60 /// # Errors
61 ///
62 /// Returns an error if:
63 /// - A required column is missing
64 /// - A column value cannot be converted to the expected Rust type
65 /// - Any other mapping error occurs
66 fn from_row(row: &Row) -> Result<Self, Error>;
67}
68
69/// Extension trait for iterating over query results as typed structs.
70///
71/// This trait is automatically implemented for any iterator of `Result<Row, Error>`.
72pub trait RowIteratorExt: Iterator<Item = Result<Row, Error>> + Sized {
73 /// Map each row to a struct implementing `FromRow`.
74 ///
75 /// # Example
76 ///
77 /// ```rust,ignore
78 /// use mssql_client::{FromRow, RowIteratorExt};
79 ///
80 /// #[derive(FromRow)]
81 /// struct User { id: i32, name: String }
82 ///
83 /// let users: Vec<User> = client
84 /// .query("SELECT id, name FROM users", &[])
85 /// .await?
86 /// .map_rows::<User>()
87 /// .collect::<Result<Vec<_>, _>>()?;
88 /// ```
89 fn map_rows<T: FromRow>(self) -> MapRows<Self, T>;
90}
91
92impl<I: Iterator<Item = Result<Row, Error>>> RowIteratorExt for I {
93 fn map_rows<T: FromRow>(self) -> MapRows<Self, T> {
94 MapRows {
95 inner: self,
96 _marker: std::marker::PhantomData,
97 }
98 }
99}
100
101/// Iterator adapter that maps rows to typed structs.
102pub struct MapRows<I, T> {
103 inner: I,
104 _marker: std::marker::PhantomData<T>,
105}
106
107impl<I, T> Iterator for MapRows<I, T>
108where
109 I: Iterator<Item = Result<Row, Error>>,
110 T: FromRow,
111{
112 type Item = Result<T, Error>;
113
114 fn next(&mut self) -> Option<Self::Item> {
115 self.inner
116 .next()
117 .map(|result| result.and_then(|row| T::from_row(&row)))
118 }
119
120 fn size_hint(&self) -> (usize, Option<usize>) {
121 self.inner.size_hint()
122 }
123}
124
125#[cfg(test)]
126#[allow(clippy::unwrap_used)]
127mod tests {
128 use super::*;
129 use crate::row::Column;
130 use mssql_types::SqlValue;
131
132 struct TestUser {
133 id: i32,
134 name: String,
135 }
136
137 impl FromRow for TestUser {
138 fn from_row(row: &Row) -> Result<Self, Error> {
139 Ok(Self {
140 id: row.get_by_name("id").map_err(Error::from)?,
141 name: row.get_by_name("name").map_err(Error::from)?,
142 })
143 }
144 }
145
146 #[test]
147 fn test_from_row_manual_impl() {
148 let columns = vec![
149 Column::new("id", 0, "INT".to_string()),
150 Column::new("name", 1, "NVARCHAR".to_string()),
151 ];
152 let row = Row::from_values(
153 columns,
154 vec![SqlValue::Int(42), SqlValue::String("Alice".to_string())],
155 );
156
157 let user = TestUser::from_row(&row).unwrap();
158 assert_eq!(user.id, 42);
159 assert_eq!(user.name, "Alice");
160 }
161
162 #[test]
163 fn test_map_rows_iterator() {
164 let columns = vec![
165 Column::new("id", 0, "INT".to_string()),
166 Column::new("name", 1, "NVARCHAR".to_string()),
167 ];
168
169 let rows = vec![
170 Ok(Row::from_values(
171 columns.clone(),
172 vec![SqlValue::Int(1), SqlValue::String("Alice".to_string())],
173 )),
174 Ok(Row::from_values(
175 columns.clone(),
176 vec![SqlValue::Int(2), SqlValue::String("Bob".to_string())],
177 )),
178 ];
179
180 let users: Vec<TestUser> = rows
181 .into_iter()
182 .map_rows::<TestUser>()
183 .collect::<Result<Vec<_>, _>>()
184 .unwrap();
185
186 assert_eq!(users.len(), 2);
187 assert_eq!(users[0].id, 1);
188 assert_eq!(users[0].name, "Alice");
189 assert_eq!(users[1].id, 2);
190 assert_eq!(users[1].name, "Bob");
191 }
192}