-
-
Notifications
You must be signed in to change notification settings - Fork 526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Allow nest
ing of structs deriving FromQueryResult
(and DerivePartialModel
)
#2179
base: master
Are you sure you want to change the base?
Changes from all commits
7208d24
521113a
5b62e75
8f9a13f
310f4f6
8c73560
f7ebc9b
be0c243
7d90323
f0587e2
d838ae1
4c5ce66
d692ded
3bd1d34
ea7329a
9ad9327
1dcab16
57a9534
673b8f1
e3c6ef3
984827a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,22 +5,81 @@ use syn::{ | |
ext::IdentExt, punctuated::Punctuated, token::Comma, Data, DataStruct, Fields, Generics, Meta, | ||
}; | ||
|
||
pub struct FromQueryResultItem { | ||
pub skip: bool, | ||
enum ItemType { | ||
Normal, | ||
Skipped, | ||
Nested, | ||
} | ||
|
||
struct FromQueryResultItem { | ||
pub typ: ItemType, | ||
pub ident: Ident, | ||
} | ||
impl ToTokens for FromQueryResultItem { | ||
|
||
/// Initially, we try to obtain the value for each field and check if it is an ordinary DB error | ||
/// (which we return immediatly), or a null error. | ||
/// | ||
/// ### Background | ||
/// | ||
/// Null errors do not necessarily mean that the deserialization as a whole fails, | ||
/// since structs embedding the current one might have wrapped the current one in an `Option`. | ||
/// In this case, we do not want to swallow other errors, which are very likely to actually be | ||
/// programming errors that should be noticed (and fixed). | ||
struct TryFromQueryResultCheck<'a>(&'a FromQueryResultItem); | ||
|
||
impl<'a> ToTokens for TryFromQueryResultCheck<'a> { | ||
fn to_tokens(&self, tokens: &mut TokenStream) { | ||
let FromQueryResultItem { ident, typ } = self.0; | ||
|
||
match typ { | ||
ItemType::Normal => { | ||
let name = ident.unraw().to_string(); | ||
tokens.extend(quote! { | ||
let #ident = match row.try_get_nullable(pre, #name) { | ||
Err(v @ sea_orm::TryGetError::DbErr(_)) => { | ||
return Err(v); | ||
} | ||
v => v, | ||
}; | ||
}); | ||
} | ||
ItemType::Skipped => { | ||
tokens.extend(quote! { | ||
let #ident = std::default::Default::default(); | ||
}); | ||
} | ||
ItemType::Nested => { | ||
let name = ident.unraw().to_string(); | ||
tokens.extend(quote! { | ||
let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(row, &format!("{pre}{}-", #name)) { | ||
Err(v @ sea_orm::TryGetError::DbErr(_)) => { | ||
return Err(v); | ||
} | ||
v => v, | ||
}; | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
|
||
struct TryFromQueryResultAssignment<'a>(&'a FromQueryResultItem); | ||
|
||
impl<'a> ToTokens for TryFromQueryResultAssignment<'a> { | ||
fn to_tokens(&self, tokens: &mut TokenStream) { | ||
let Self { ident, skip } = self; | ||
if *skip { | ||
tokens.extend(quote! { | ||
#ident: std::default::Default::default(), | ||
}); | ||
} else { | ||
let name = ident.unraw().to_string(); | ||
tokens.extend(quote! { | ||
#ident: row.try_get(pre, #name)?, | ||
}); | ||
let FromQueryResultItem { ident, typ, .. } = self.0; | ||
|
||
match typ { | ||
ItemType::Normal | ItemType::Nested => { | ||
tokens.extend(quote! { | ||
#ident: #ident?, | ||
}); | ||
} | ||
ItemType::Skipped => { | ||
tokens.extend(quote! { | ||
#ident, | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -31,7 +90,7 @@ pub fn expand_derive_from_query_result( | |
data: Data, | ||
generics: Generics, | ||
) -> syn::Result<TokenStream> { | ||
let fields = match data { | ||
let parsed_fields = match data { | ||
Data::Struct(DataStruct { | ||
fields: Fields::Named(named), | ||
.. | ||
|
@@ -42,40 +101,54 @@ pub fn expand_derive_from_query_result( | |
}) | ||
} | ||
}; | ||
let mut field = Vec::with_capacity(fields.len()); | ||
|
||
for parsed_field in fields.into_iter() { | ||
let mut skip = false; | ||
let mut fields = Vec::with_capacity(parsed_fields.len()); | ||
for parsed_field in parsed_fields.into_iter() { | ||
let mut typ = ItemType::Normal; | ||
for attr in parsed_field.attrs.iter() { | ||
if !attr.path().is_ident("sea_orm") { | ||
continue; | ||
} | ||
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) { | ||
for meta in list.iter() { | ||
skip = meta.exists("skip"); | ||
if meta.exists("skip") { | ||
typ = ItemType::Skipped; | ||
} else if meta.exists("nested") { | ||
typ = ItemType::Nested; | ||
} | ||
} | ||
} | ||
} | ||
let ident = format_ident!("{}", parsed_field.ident.unwrap().to_string()); | ||
field.push(FromQueryResultItem { skip, ident }); | ||
fields.push(FromQueryResultItem { typ, ident }); | ||
} | ||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); | ||
|
||
let ident_try_init: Vec<_> = fields.iter().map(TryFromQueryResultCheck).collect(); | ||
let ident_try_assign: Vec<_> = fields.iter().map(TryFromQueryResultAssignment).collect(); | ||
|
||
Ok(quote!( | ||
#[automatically_derived] | ||
impl #impl_generics sea_orm::FromQueryResult for #ident #ty_generics #where_clause { | ||
fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result<Self, sea_orm::DbErr> { | ||
fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> Result<Self, sea_orm::DbErr> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use std::result::Result There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valid criticism. The correct way is to use |
||
Ok(Self::from_query_result_nullable(row, pre)?) | ||
} | ||
|
||
fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> Result<Self, sea_orm::TryGetError> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, use std::result::Result |
||
#(#ident_try_init)* | ||
|
||
Ok(Self { | ||
#(#field)* | ||
#(#ident_try_assign)* | ||
}) | ||
} | ||
} | ||
)) | ||
} | ||
mod util { | ||
|
||
pub(super) mod util { | ||
use syn::Meta; | ||
|
||
pub(super) trait GetMeta { | ||
pub trait GetMeta { | ||
fn exists(&self, k: &str) -> bool; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the package name to the prefix makes the whole thing unusable unless you are using it with a
DerivePartialModel
because the base model don't add the entity name in front of the column. An integration test would have caught that easily.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nested
only makes sense when used withDerivePartialModel
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this is about using a struct with
nested
in it ininto_model::<..>()
, I guess? Yup, that is most likely going to break, but due to the name collision thing above there is not really anything I can do about it. The relationship betweenFromQueryResult
andDerivePartialModel
is weird and they should arguably be mutually exclusive, but that would involve copying the entire code fromFromQueryResult
intoDerivePartialModel
. That would also mean doing things like theskip
I proposed elsewhere twice: #2167