Skip to content

Commit 12987bf

Browse files
authored
Merge pull request #5 from remkop22/rename-attr
Rename attr
2 parents 2e786ba + 922c13a commit 12987bf

File tree

3 files changed

+157
-117
lines changed

3 files changed

+157
-117
lines changed

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ struct Todo {
2626
author_id: i32,
2727
}
2828

29-
let row = client.query_one("SELECT todo_id, text, author_id FROM todos").unwrap();
29+
let row = client.query_one("SELECT todo_id, text, author_id FROM todos", &[]).unwrap();
3030

3131
// Pass a row with the correct columns.
3232
let todo = Todo::from_row(&row);
3333

34-
let row = client.query_one("SELECT foo FROM bar").unwrap();
34+
let row = client.query_one("SELECT foo FROM bar", &[]).unwrap();
3535

3636
// Use `try_from_row` if the operation could fail.
3737
let todo = Todo::try_from_row(&row);
@@ -59,7 +59,29 @@ struct User {
5959
username: String
6060
}
6161

62-
let row = client.query_one("SELECT todo_id, text, user_id, username FROM todos t, users u WHERE t.author_id = u.user_id").unwrap();
62+
let row = client.query_one("SELECT todo_id, text, user_id, username FROM todos t, users u WHERE t.author_id = u.user_id", &[]).unwrap();
6363
let todo = Todo::from_row(&row);
6464
```
6565

66+
If a the struct contains a field with a name that differs from the name of the sql column, you can use the `#[from_row(rename = "..")]` attribute.
67+
68+
When a field in your struct has a type `T` that doesn't implement `FromSql` or `FromRow` but
69+
it does impement `T: From<C>` or `T: TryFrom<c>`, and `C` does implment `FromSql` or `FromRow`
70+
you can use `#[from_row(from = "C")]` or `#[from_row(try_from = "C")]`. This will use type `C` to extract it from the row and
71+
then finally converts it into `T`.
72+
73+
```rust
74+
75+
struct Todo {
76+
// If the postgres column is named `todo_id`.
77+
#[from_row(rename = "todo_id")]
78+
id: i32,
79+
// If the postgres column is `VARCHAR`, it will be decoded to `String`,
80+
// using `FromSql` and then converted to `Vec<u8>` using `std::convert::From`.
81+
#[from_row(from = "String")]
82+
todo: Vec<u8>
83+
}
84+
85+
```
86+
87+

postgres-from-row-derive/src/lib.rs

Lines changed: 131 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use darling::ast::{self, Style};
2-
use darling::{FromDeriveInput, FromField, ToTokens};
1+
use darling::{ast::Data, Error, FromDeriveInput, FromField, ToTokens};
32
use proc_macro::TokenStream;
3+
use proc_macro2::TokenStream as TokenStream2;
44
use quote::quote;
5-
use syn::{parse_macro_input, DeriveInput, Ident};
5+
use syn::{parse_macro_input, DeriveInput, Ident, Result};
66

77
#[proc_macro_derive(FromRowTokioPostgres, attributes(from_row))]
88
pub fn derive_from_row_tokio_postgres(input: TokenStream) -> TokenStream {
@@ -14,6 +14,7 @@ pub fn derive_from_row_postgres(input: TokenStream) -> TokenStream {
1414
derive_from_row(input, quote::format_ident!("postgres"))
1515
}
1616

17+
/// Calls the fallible entry point and writes any errors to the tokenstream.
1718
fn derive_from_row(input: TokenStream, module: Ident) -> TokenStream {
1819
let derive_input = parse_macro_input!(input as DeriveInput);
1920
match try_derive_from_row(&derive_input, module) {
@@ -22,11 +23,16 @@ fn derive_from_row(input: TokenStream, module: Ident) -> TokenStream {
2223
}
2324
}
2425

25-
fn try_derive_from_row(input: &DeriveInput, module: Ident) -> Result<TokenStream, darling::Error> {
26+
/// Fallible entry point for generating a `FromRow` implementation
27+
fn try_derive_from_row(
28+
input: &DeriveInput,
29+
module: Ident,
30+
) -> std::result::Result<TokenStream, Error> {
2631
let from_row_derive = DeriveFromRow::from_derive_input(input)?;
27-
from_row_derive.generate(module)
32+
Ok(from_row_derive.generate(module)?)
2833
}
2934

35+
/// Main struct for deriving `FromRow` for a struct.
3036
#[derive(Debug, FromDeriveInput)]
3137
#[darling(
3238
attributes(from_row),
@@ -36,59 +42,60 @@ fn try_derive_from_row(input: &DeriveInput, module: Ident) -> Result<TokenStream
3642
struct DeriveFromRow {
3743
ident: syn::Ident,
3844
generics: syn::Generics,
39-
data: ast::Data<(), FromRowField>,
45+
data: Data<(), FromRowField>,
4046
}
4147

4248
impl DeriveFromRow {
43-
fn generate(self, module: Ident) -> Result<TokenStream, darling::Error> {
44-
let ident = &self.ident;
49+
/// Validates all fields
50+
fn validate(&self) -> Result<()> {
51+
for field in self.fields() {
52+
field.validate()?;
53+
}
4554

46-
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
55+
Ok(())
56+
}
4757

48-
let fields = self
49-
.data
50-
.take_struct()
51-
.ok_or_else(|| darling::Error::unsupported_shape("enum").with_span(&self.ident))?;
58+
/// Generates any additional where clause predicates needed for the fields in this struct.
59+
fn predicates(&self, module: &Ident) -> Result<Vec<TokenStream2>> {
60+
let mut predicates = Vec::new();
5261

53-
let fields = match fields.style {
54-
Style::Unit => {
55-
return Err(darling::Error::unsupported_shape("unit struct").with_span(&self.ident))
56-
}
57-
Style::Tuple => {
58-
return Err(darling::Error::unsupported_shape("tuple struct").with_span(&self.ident))
59-
}
60-
Style::Struct => fields.fields,
61-
};
62+
for field in self.fields() {
63+
field.add_predicates(&module, &mut predicates)?;
64+
}
6265

63-
let from_row_fields = fields
66+
Ok(predicates)
67+
}
68+
69+
/// Provides a slice of this struct's fields.
70+
fn fields(&self) -> &[FromRowField] {
71+
match &self.data {
72+
Data::Struct(fields) => &fields.fields,
73+
_ => panic!("invalid shape"),
74+
}
75+
}
76+
77+
/// Generate the `FromRow` implementation.
78+
fn generate(self, module: Ident) -> Result<TokenStream> {
79+
self.validate()?;
80+
81+
let ident = &self.ident;
82+
83+
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
84+
let original_predicates = where_clause.clone().map(|w| &w.predicates).into_iter();
85+
let predicates = self.predicates(&module)?;
86+
87+
let from_row_fields = self
88+
.fields()
6489
.iter()
6590
.map(|f| f.generate_from_row(&module))
6691
.collect::<syn::Result<Vec<_>>>()?;
6792

68-
let try_from_row_fields = fields
93+
let try_from_row_fields = self
94+
.fields()
6995
.iter()
7096
.map(|f| f.generate_try_from_row(&module))
7197
.collect::<syn::Result<Vec<_>>>()?;
7298

73-
let original_predicates = where_clause.clone().map(|w| &w.predicates).into_iter();
74-
let mut predicates = Vec::new();
75-
76-
for field in fields.iter() {
77-
let target_ty = &field.target_ty()?;
78-
let ty = &field.ty;
79-
predicates.push(if field.flatten {
80-
quote! (#target_ty: postgres_from_row::FromRow)
81-
} else {
82-
quote! (#target_ty: for<'a> #module::types::FromSql<'a>)
83-
});
84-
85-
if field.from.is_some() {
86-
predicates.push(quote!(#ty: std::convert::From<#target_ty>))
87-
} else if field.try_from.is_some() {
88-
predicates.push(quote!(#ty: std::convert::From<#target_ty>))
89-
}
90-
}
91-
9299
Ok(quote! {
93100
impl #impl_generics postgres_from_row::FromRow for #ident #ty_generics where #(#original_predicates),* #(#predicates),* {
94101

@@ -109,19 +116,52 @@ impl DeriveFromRow {
109116
}
110117
}
111118

119+
/// A single field inside of a struct that derives `FromRow`
112120
#[derive(Debug, FromField)]
113121
#[darling(attributes(from_row), forward_attrs(allow, doc, cfg))]
114122
struct FromRowField {
123+
/// The identifier of this field.
115124
ident: Option<syn::Ident>,
125+
/// The type specified in this field.
116126
ty: syn::Type,
127+
/// Wether to flatten this field. Flattening means calling the `FromRow` implementation
128+
/// of `self.ty` instead of extracting it directly from the row.
117129
#[darling(default)]
118130
flatten: bool,
131+
/// Optionaly use this type as the target for `FromRow` or `FromSql`, and then
132+
/// call `TryFrom::try_from` to convert it the `self.ty`.
119133
try_from: Option<String>,
134+
/// Optionaly use this type as the target for `FromRow` or `FromSql`, and then
135+
/// call `From::from` to convert it the `self.ty`.
120136
from: Option<String>,
137+
/// Override the name of the actual sql column instead of using `self.ident`.
138+
/// Is not compatible with `flatten` since no column is needed there.
139+
rename: Option<String>,
121140
}
122141

123142
impl FromRowField {
124-
fn target_ty(&self) -> syn::Result<proc_macro2::TokenStream> {
143+
/// Checks wether this field has a valid combination of attributes
144+
fn validate(&self) -> Result<()> {
145+
if self.from.is_some() && self.try_from.is_some() {
146+
return Err(Error::custom(
147+
r#"can't combine `#[from_row(from = "..")]` with `#[from_row(try_from = "..")]`"#,
148+
)
149+
.into());
150+
}
151+
152+
if self.rename.is_some() && self.flatten {
153+
return Err(Error::custom(
154+
r#"can't combine `#[from_row(flatten)]` with `#[from_row(rename = "..")]`"#,
155+
)
156+
.into());
157+
}
158+
159+
Ok(())
160+
}
161+
162+
/// Returns a tokenstream of the type that should be returned from either
163+
/// `FromRow` (when using `flatten`) or `FromSql`.
164+
fn target_ty(&self) -> Result<TokenStream2> {
125165
if let Some(from) = &self.from {
126166
Ok(from.parse()?)
127167
} else if let Some(try_from) = &self.try_from {
@@ -131,17 +171,56 @@ impl FromRowField {
131171
}
132172
}
133173

134-
fn generate_from_row(&self, module: &Ident) -> syn::Result<proc_macro2::TokenStream> {
174+
/// Returns the name that maps to the actuall sql column
175+
/// By default this is the same as the rust field name but can be overwritten by `#[from_row(rename = "..")]`.
176+
fn column_name(&self) -> String {
177+
self.rename
178+
.as_ref()
179+
.map(Clone::clone)
180+
.unwrap_or_else(|| self.ident.as_ref().unwrap().to_string())
181+
}
182+
183+
/// Pushes the needed where clause predicates for this field.
184+
///
185+
/// By default this is `T: for<'a> postgres::types::FromSql<'a>`,
186+
/// when using `flatten` it's: `T: postgres_from_row::FromRow`
187+
/// and when using either `from` or `try_from` attributes it additionally pushes this bound:
188+
/// `T: std::convert::From<R>`, where `T` is the type specified in the struct and `R` is the
189+
/// type specified in the `[try]_from` attribute.
190+
fn add_predicates(&self, module: &Ident, predicates: &mut Vec<TokenStream2>) -> Result<()> {
191+
let target_ty = &self.target_ty()?;
192+
let ty = &self.ty;
193+
194+
predicates.push(if self.flatten {
195+
quote! (#target_ty: postgres_from_row::FromRow)
196+
} else {
197+
quote! (#target_ty: for<'a> #module::types::FromSql<'a>)
198+
});
199+
200+
if self.from.is_some() {
201+
predicates.push(quote!(#ty: std::convert::From<#target_ty>))
202+
} else if self.try_from.is_some() {
203+
let try_from = quote!(std::convert::TryFrom<#target_ty>);
204+
205+
predicates.push(quote!(#ty: #try_from));
206+
predicates.push(quote!(#module::Error: std::convert::From<<#ty as #try_from>::Error>));
207+
predicates.push(quote!(<#ty as #try_from>::Error: std::fmt::Debug));
208+
}
209+
210+
Ok(())
211+
}
212+
213+
/// Generate the line needed to retrievee this field from a row when calling `from_row`.
214+
fn generate_from_row(&self, module: &Ident) -> Result<TokenStream2> {
135215
let ident = self.ident.as_ref().unwrap();
136-
let str_ident = ident.to_string();
216+
let column_name = self.column_name();
137217
let field_ty = &self.ty;
138-
139218
let target_ty = self.target_ty()?;
140219

141220
let mut base = if self.flatten {
142221
quote!(<#target_ty as postgres_from_row::FromRow>::from_row(row))
143222
} else {
144-
quote!(#module::Row::get::<&str, #target_ty>(row, #str_ident))
223+
quote!(#module::Row::get::<&str, #target_ty>(row, #column_name))
145224
};
146225

147226
if self.from.is_some() {
@@ -153,16 +232,17 @@ impl FromRowField {
153232
Ok(quote!(#ident: #base))
154233
}
155234

156-
fn generate_try_from_row(&self, module: &Ident) -> syn::Result<proc_macro2::TokenStream> {
235+
/// Generate the line needed to retrieve this field from a row when calling `try_from_row`.
236+
fn generate_try_from_row(&self, module: &Ident) -> Result<TokenStream2> {
157237
let ident = self.ident.as_ref().unwrap();
158-
let str_ident = ident.to_string();
238+
let column_name = self.column_name();
159239
let field_ty = &self.ty;
160240
let target_ty = self.target_ty()?;
161241

162242
let mut base = if self.flatten {
163243
quote!(<#target_ty as postgres_from_row::FromRow>::try_from_row(row)?)
164244
} else {
165-
quote!(#module::Row::try_get::<&str, #target_ty>(row, #str_ident)?)
245+
quote!(#module::Row::try_get::<&str, #target_ty>(row, #column_name)?)
166246
};
167247

168248
if self.from.is_some() {

src/lib.rs

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,5 @@
11
#![deny(missing_docs)]
2-
//! Derive [`FromRow`] to generate a mapping between a struct and postgres rows.
3-
//!
4-
//! This crate works with [postgres](<https://docs.rs/postgres>) by default.
5-
//!
6-
//! ```toml
7-
//! [dependencies]
8-
//! postgres_from_row = "0.4.0"
9-
//! ```
10-
//!
11-
//! If you want to use it with [tokio-postgres](<https://docs.rs/tokio-postgres>), enable it like so:
12-
//!
13-
//! ```toml
14-
//! [dependencies]
15-
//! postgres_from_row = { version = "0.4.0", default_features = false, features = ["tokio-postgres"] }
16-
//! ```
17-
//! # Examples
18-
//! ```rust
19-
//! use postgres_from_row::FromRow;
20-
//!
21-
//! #[derive(FromRow)]
22-
//! struct Todo {
23-
//! todo_id: i32,
24-
//! text: String,
25-
//! author_id: i32,
26-
//! }
27-
//!
28-
//! let row = client.query_one("SELECT todo_id, text, author_id FROM todos").unwrap();
29-
//!
30-
//! // Pass a row with the correct columns.
31-
//! let todo = Todo::from_row(&row);
32-
//!
33-
//! let row = client.query_one("SELECT foo FROM bar").unwrap();
34-
//!
35-
//! // Use `try_from_row` if the operation could fail.
36-
//! let todo = Todo::try_from_row(&row);
37-
//! assert!(todo.is_err());
38-
//! ```
39-
//!
40-
//! Each field need's to implement [`postgres::types::FromSql`], as this will be used to convert a
41-
//! single column to the specified type. If you want to override this behavior and delegate it to a
42-
//! nested structure that also implements [`FromRow`], use `#[from_row(flatten)]`:
43-
//!
44-
//! ```rust
45-
//! use postgres_from_row::FromRow;
46-
//!
47-
//! #[derive(FromRow)]
48-
//! struct Todo {
49-
//! todo_id: i32,
50-
//! text: String,
51-
//! #[from_row(flatten)]
52-
//! author: User
53-
//! }
54-
//!
55-
//! #[derive(FromRow)]
56-
//! struct User {
57-
//! user_id: i32,
58-
//! username: String
59-
//! }
60-
//!
61-
//! let row = client.query_one("SELECT todo_id, text, user_id, username FROM todos t, users u WHERE t.author_id = u.user_id").unwrap();
62-
//! let todo = Todo::from_row(&row);
63-
//! ```
64-
//!
2+
#![doc = include_str!("../README.md")]
653

664
#[cfg(feature = "postgres")]
675
mod active_postgres {

0 commit comments

Comments
 (0)