Skip to content

Commit 729fa50

Browse files
authored
Merge pull request #985 from muzarski/from_row_for_unnamed_structs
derive `FromRow` for structs with unnamed fields
2 parents 1fcadc9 + 12a0436 commit 729fa50

File tree

4 files changed

+121
-50
lines changed

4 files changed

+121
-50
lines changed

scylla-cql/src/frame/response/cql_to_rust.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,4 +1000,24 @@ mod tests {
10001000
})
10011001
);
10021002
}
1003+
1004+
#[test]
1005+
fn unnamed_struct_from_row() {
1006+
#[derive(FromRow)]
1007+
struct MyRow(i32, Option<String>, Option<Vec<i32>>);
1008+
1009+
let row = Row {
1010+
columns: vec![
1011+
Some(CqlValue::Int(16)),
1012+
None,
1013+
Some(CqlValue::Set(vec![CqlValue::Int(1), CqlValue::Int(2)])),
1014+
],
1015+
};
1016+
1017+
let my_row: MyRow = MyRow::from_row(row).unwrap();
1018+
1019+
assert_eq!(my_row.0, 16);
1020+
assert_eq!(my_row.1, None);
1021+
assert_eq!(my_row.2, Some(vec![1, 2]));
1022+
}
10031023
}

scylla-macros/src/from_row.rs

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,68 @@ use syn::{spanned::Spanned, DeriveInput};
66
pub(crate) fn from_row_derive(tokens_input: TokenStream) -> Result<TokenStream, syn::Error> {
77
let item = syn::parse::<DeriveInput>(tokens_input)?;
88
let path = crate::parser::get_path(&item)?;
9-
let struct_fields = crate::parser::parse_named_fields(&item, "FromRow")?;
9+
let struct_fields = crate::parser::parse_struct_fields(&item, "FromRow")?;
1010

1111
let struct_name = &item.ident;
1212
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
1313

14-
// Generates tokens for field_name: field_type::from_cql(vals_iter.next().ok_or(...)?), ...
15-
let set_fields_code = struct_fields.named.iter().map(|field| {
16-
let field_name = &field.ident;
17-
let field_type = &field.ty;
18-
19-
quote_spanned! {field.span() =>
20-
#field_name: {
21-
let (col_ix, col_value) = vals_iter
22-
.next()
23-
.unwrap(); // vals_iter size is checked before this code is reached, so
24-
// it is safe to unwrap
25-
26-
<#field_type as FromCqlVal<::std::option::Option<CqlValue>>>::from_cql(col_value)
27-
.map_err(|e| FromRowError::BadCqlVal {
28-
err: e,
29-
column: col_ix,
30-
})?
31-
},
14+
// Generates a token that sets the values of struct fields: field_type::from_cql(...)
15+
let (fill_struct_code, fields_count) = match struct_fields {
16+
crate::parser::StructFields::Named(fields) => {
17+
let set_fields_code = fields.named.iter().map(|field| {
18+
let field_name = &field.ident;
19+
let field_type = &field.ty;
20+
21+
quote_spanned! {field.span() =>
22+
#field_name: {
23+
let (col_ix, col_value) = vals_iter
24+
.next()
25+
.unwrap(); // vals_iter size is checked before this code is reached, so
26+
// it is safe to unwrap
27+
28+
<#field_type as FromCqlVal<::std::option::Option<CqlValue>>>::from_cql(col_value)
29+
.map_err(|e| FromRowError::BadCqlVal {
30+
err: e,
31+
column: col_ix,
32+
})?
33+
},
34+
}
35+
});
36+
37+
// This generates: { field1: {field1_code}, field2: {field2_code}, ...}
38+
let fill_struct_code = quote! {
39+
{#(#set_fields_code)*}
40+
};
41+
(fill_struct_code, fields.named.len())
42+
}
43+
crate::parser::StructFields::Unnamed(fields) => {
44+
let set_fields_code = fields.unnamed.iter().map(|field| {
45+
let field_type = &field.ty;
46+
47+
quote_spanned! {field.span() =>
48+
{
49+
let (col_ix, col_value) = vals_iter
50+
.next()
51+
.unwrap(); // vals_iter size is checked before this code is reached, so
52+
// it is safe to unwrap
53+
54+
<#field_type as FromCqlVal<::std::option::Option<CqlValue>>>::from_cql(col_value)
55+
.map_err(|e| FromRowError::BadCqlVal {
56+
err: e,
57+
column: col_ix,
58+
})?
59+
},
60+
}
61+
});
62+
63+
// This generates: ( {<field1_code>}, {<field2_code>}, ... )
64+
let fill_struct_code = quote! {
65+
(#(#set_fields_code)*)
66+
};
67+
(fill_struct_code, fields.unnamed.len())
3268
}
33-
});
69+
};
3470

35-
let fields_count = struct_fields.named.len();
3671
let generated = quote! {
3772
impl #impl_generics #path::FromRow for #struct_name #ty_generics #where_clause {
3873
fn from_row(row: #path::Row)
@@ -49,9 +84,7 @@ pub(crate) fn from_row_derive(tokens_input: TokenStream) -> Result<TokenStream,
4984
}
5085
let mut vals_iter = row.columns.into_iter().enumerate();
5186

52-
Ok(#struct_name {
53-
#(#set_fields_code)*
54-
})
87+
Ok(#struct_name #fill_struct_code)
5588
}
5689
}
5790
};

scylla-macros/src/parser.rs

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,53 @@
1-
use syn::{Data, DeriveInput, ExprLit, Fields, FieldsNamed, Lit};
1+
use syn::{Data, DeriveInput, ExprLit, Fields, FieldsNamed, FieldsUnnamed, Lit};
22
use syn::{Expr, Meta};
33

4-
/// Parses the tokens_input to a DeriveInput and returns the struct name from which it derives and
5-
/// the named fields
4+
/// Parses a struct DeriveInput and returns named fields of this struct.
65
pub(crate) fn parse_named_fields<'a>(
76
input: &'a DeriveInput,
87
current_derive: &str,
98
) -> Result<&'a FieldsNamed, syn::Error> {
9+
let create_err_msg = || {
10+
format!(
11+
"derive({}) works only for structs with named fields",
12+
current_derive
13+
)
14+
};
15+
1016
match &input.data {
1117
Data::Struct(data) => match &data.fields {
1218
Fields::Named(named_fields) => Ok(named_fields),
13-
_ => Err(syn::Error::new_spanned(
14-
data.struct_token,
15-
format!(
16-
"derive({}) works only for structs with named fields",
17-
current_derive
18-
),
19-
)),
19+
_ => Err(syn::Error::new_spanned(data.struct_token, create_err_msg())),
20+
},
21+
Data::Enum(e) => Err(syn::Error::new_spanned(e.enum_token, create_err_msg())),
22+
Data::Union(u) => Err(syn::Error::new_spanned(u.union_token, create_err_msg())),
23+
}
24+
}
25+
26+
pub(crate) enum StructFields<'a> {
27+
Named(&'a FieldsNamed),
28+
Unnamed(&'a FieldsUnnamed),
29+
}
30+
31+
/// Parses a struct DeriveInput and returns fields (both named and unnamed) of this struct.
32+
pub(crate) fn parse_struct_fields<'a>(
33+
input: &'a DeriveInput,
34+
current_derive: &str,
35+
) -> Result<StructFields<'a>, syn::Error> {
36+
let create_err_msg = || {
37+
format!(
38+
"derive({}) works only for structs with fields",
39+
current_derive
40+
)
41+
};
42+
43+
match &input.data {
44+
Data::Struct(data) => match &data.fields {
45+
Fields::Named(named_fields) => Ok(StructFields::Named(named_fields)),
46+
Fields::Unnamed(unnamed_fields) => Ok(StructFields::Unnamed(unnamed_fields)),
47+
Fields::Unit => Err(syn::Error::new_spanned(data.struct_token, create_err_msg())),
2048
},
21-
Data::Enum(e) => Err(syn::Error::new_spanned(
22-
e.enum_token,
23-
format!(
24-
"derive({}) works only for structs with named fields",
25-
current_derive
26-
),
27-
)),
28-
Data::Union(u) => Err(syn::Error::new_spanned(
29-
u.union_token,
30-
format!(
31-
"derive({}) works only for structs with named fields",
32-
current_derive
33-
),
34-
)),
49+
Data::Enum(e) => Err(syn::Error::new_spanned(e.enum_token, create_err_msg())),
50+
Data::Union(u) => Err(syn::Error::new_spanned(u.union_token, create_err_msg())),
3551
}
3652
}
3753

scylla/src/macros.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
/// #[derive(FromRow)] derives FromRow for struct
1+
/// Derive macro for the [`FromRow`](crate::frame::response::cql_to_rust::FromRow) trait
2+
/// which deserializes a row to given Rust structure.
23
///
3-
/// Works only on simple structs without generics etc
4+
/// It is supported for structs with either named or unnamed fields.
5+
/// It works only for simple structs without generics etc.
46
///
57
/// ---
68
///

0 commit comments

Comments
 (0)