Skip to content

Commit

Permalink
Merge pull request #985 from muzarski/from_row_for_unnamed_structs
Browse files Browse the repository at this point in the history
derive `FromRow` for structs with unnamed fields
  • Loading branch information
wprzytula authored May 7, 2024
2 parents 1fcadc9 + 12a0436 commit 729fa50
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 50 deletions.
20 changes: 20 additions & 0 deletions scylla-cql/src/frame/response/cql_to_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,4 +1000,24 @@ mod tests {
})
);
}

#[test]
fn unnamed_struct_from_row() {
#[derive(FromRow)]
struct MyRow(i32, Option<String>, Option<Vec<i32>>);

let row = Row {
columns: vec![
Some(CqlValue::Int(16)),
None,
Some(CqlValue::Set(vec![CqlValue::Int(1), CqlValue::Int(2)])),
],
};

let my_row: MyRow = MyRow::from_row(row).unwrap();

assert_eq!(my_row.0, 16);
assert_eq!(my_row.1, None);
assert_eq!(my_row.2, Some(vec![1, 2]));
}
}
81 changes: 57 additions & 24 deletions scylla-macros/src/from_row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,68 @@ use syn::{spanned::Spanned, DeriveInput};
pub(crate) fn from_row_derive(tokens_input: TokenStream) -> Result<TokenStream, syn::Error> {
let item = syn::parse::<DeriveInput>(tokens_input)?;
let path = crate::parser::get_path(&item)?;
let struct_fields = crate::parser::parse_named_fields(&item, "FromRow")?;
let struct_fields = crate::parser::parse_struct_fields(&item, "FromRow")?;

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

// Generates tokens for field_name: field_type::from_cql(vals_iter.next().ok_or(...)?), ...
let set_fields_code = struct_fields.named.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;

quote_spanned! {field.span() =>
#field_name: {
let (col_ix, col_value) = vals_iter
.next()
.unwrap(); // vals_iter size is checked before this code is reached, so
// it is safe to unwrap

<#field_type as FromCqlVal<::std::option::Option<CqlValue>>>::from_cql(col_value)
.map_err(|e| FromRowError::BadCqlVal {
err: e,
column: col_ix,
})?
},
// Generates a token that sets the values of struct fields: field_type::from_cql(...)
let (fill_struct_code, fields_count) = match struct_fields {
crate::parser::StructFields::Named(fields) => {
let set_fields_code = fields.named.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;

quote_spanned! {field.span() =>
#field_name: {
let (col_ix, col_value) = vals_iter
.next()
.unwrap(); // vals_iter size is checked before this code is reached, so
// it is safe to unwrap

<#field_type as FromCqlVal<::std::option::Option<CqlValue>>>::from_cql(col_value)
.map_err(|e| FromRowError::BadCqlVal {
err: e,
column: col_ix,
})?
},
}
});

// This generates: { field1: {field1_code}, field2: {field2_code}, ...}
let fill_struct_code = quote! {
{#(#set_fields_code)*}
};
(fill_struct_code, fields.named.len())
}
crate::parser::StructFields::Unnamed(fields) => {
let set_fields_code = fields.unnamed.iter().map(|field| {
let field_type = &field.ty;

quote_spanned! {field.span() =>
{
let (col_ix, col_value) = vals_iter
.next()
.unwrap(); // vals_iter size is checked before this code is reached, so
// it is safe to unwrap

<#field_type as FromCqlVal<::std::option::Option<CqlValue>>>::from_cql(col_value)
.map_err(|e| FromRowError::BadCqlVal {
err: e,
column: col_ix,
})?
},
}
});

// This generates: ( {<field1_code>}, {<field2_code>}, ... )
let fill_struct_code = quote! {
(#(#set_fields_code)*)
};
(fill_struct_code, fields.unnamed.len())
}
});
};

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

Ok(#struct_name {
#(#set_fields_code)*
})
Ok(#struct_name #fill_struct_code)
}
}
};
Expand Down
64 changes: 40 additions & 24 deletions scylla-macros/src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
use syn::{Data, DeriveInput, ExprLit, Fields, FieldsNamed, Lit};
use syn::{Data, DeriveInput, ExprLit, Fields, FieldsNamed, FieldsUnnamed, Lit};
use syn::{Expr, Meta};

/// Parses the tokens_input to a DeriveInput and returns the struct name from which it derives and
/// the named fields
/// Parses a struct DeriveInput and returns named fields of this struct.
pub(crate) fn parse_named_fields<'a>(
input: &'a DeriveInput,
current_derive: &str,
) -> Result<&'a FieldsNamed, syn::Error> {
let create_err_msg = || {
format!(
"derive({}) works only for structs with named fields",
current_derive
)
};

match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(named_fields) => Ok(named_fields),
_ => Err(syn::Error::new_spanned(
data.struct_token,
format!(
"derive({}) works only for structs with named fields",
current_derive
),
)),
_ => Err(syn::Error::new_spanned(data.struct_token, create_err_msg())),
},
Data::Enum(e) => Err(syn::Error::new_spanned(e.enum_token, create_err_msg())),
Data::Union(u) => Err(syn::Error::new_spanned(u.union_token, create_err_msg())),
}
}

pub(crate) enum StructFields<'a> {
Named(&'a FieldsNamed),
Unnamed(&'a FieldsUnnamed),
}

/// Parses a struct DeriveInput and returns fields (both named and unnamed) of this struct.
pub(crate) fn parse_struct_fields<'a>(
input: &'a DeriveInput,
current_derive: &str,
) -> Result<StructFields<'a>, syn::Error> {
let create_err_msg = || {
format!(
"derive({}) works only for structs with fields",
current_derive
)
};

match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(named_fields) => Ok(StructFields::Named(named_fields)),
Fields::Unnamed(unnamed_fields) => Ok(StructFields::Unnamed(unnamed_fields)),
Fields::Unit => Err(syn::Error::new_spanned(data.struct_token, create_err_msg())),
},
Data::Enum(e) => Err(syn::Error::new_spanned(
e.enum_token,
format!(
"derive({}) works only for structs with named fields",
current_derive
),
)),
Data::Union(u) => Err(syn::Error::new_spanned(
u.union_token,
format!(
"derive({}) works only for structs with named fields",
current_derive
),
)),
Data::Enum(e) => Err(syn::Error::new_spanned(e.enum_token, create_err_msg())),
Data::Union(u) => Err(syn::Error::new_spanned(u.union_token, create_err_msg())),
}
}

Expand Down
6 changes: 4 additions & 2 deletions scylla/src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/// #[derive(FromRow)] derives FromRow for struct
/// Derive macro for the [`FromRow`](crate::frame::response::cql_to_rust::FromRow) trait
/// which deserializes a row to given Rust structure.
///
/// Works only on simple structs without generics etc
/// It is supported for structs with either named or unnamed fields.
/// It works only for simple structs without generics etc.
///
/// ---
///
Expand Down

0 comments on commit 729fa50

Please sign in to comment.