-
-
Notifications
You must be signed in to change notification settings - Fork 568
feat: Allow nest
ing of structs deriving FromQueryResult
(and DerivePartialModel
)
#2179
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
Changes from all commits
e6bcd1b
f8cbf35
1bb14b1
aba5bec
24306ca
765cab1
06bdfca
002bd05
009d21b
f5e9360
9248ab6
585c92f
fef8999
1b8c231
27a5cf4
e210c24
804b44e
8064a74
e8dbaac
1b816b4
8db6ba8
34b76a6
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 ToTokens for TryFromQueryResultCheck<'_> { | ||
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 ToTokens for TryFromQueryResultAssignment<'_> { | ||
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: #2167There 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.
I dont remember what this was really but it didnt work when I wanted to do:
I fixed it in my fork by removing the format,see caido@30cdea1#diff-50509553d7ac1acce8ee99595b98afc8307096fe6fa1a85056e00a29eba829e1R53
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.
Unfortunately, this change does not work with the plan model structs (and I don't know an easy way to make it work). You need the prefixing because otherwise you get name collisions between common names like "id" and such, so the
format
cannot be removed.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.
@tyt2y3 thoughts? It would be important to make it work since not all nested need to derive the full model.
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.
I think I might get it now. so there are two scenarios:
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.
I think the solution is for
DerivePartialModel
to also assumes the function ofFromQueryResult
.So we will no longer recommend using both macros together.