Cli serde skip deserialize for primary key option (#1186) (#1318)

* Cli serde skip deserialize for primary key option (#1186)

* Add CLI option to skip primary keys with serde

Implements: https://github.com/SeaQL/sea-orm/issues/841

* Codegen: fix tests

* complete skip_deserialize cli feature

* run fmt

* fix tests

Co-authored-by: witcher <witcher@wiredspace.de>

* [cli] should be `#[serde(skip_deserializing)]`

* [CLI] code refactor

* [cli] rename

Co-authored-by: Isaiah Gamble <77396670+tsar-boomba@users.noreply.github.com>
Co-authored-by: witcher <witcher@wiredspace.de>
This commit is contained in:
Billy Chan 2022-12-19 22:11:45 +08:00 committed by GitHub
parent 1f27837f49
commit 3f00725ee2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 104 additions and 17 deletions

View File

@ -228,6 +228,13 @@ pub enum GenerateSubcommands {
help = "Generate index file as `lib.rs` instead of `mod.rs`." help = "Generate index file as `lib.rs` instead of `mod.rs`."
)] )]
lib: bool, lib: bool,
#[clap(
action,
long,
help = "Generate a serde field attribute, '#[serde(skip_deserializing)]', for the primary key fields to skip them during deserialization, this flag will be affective only when '--with-serde' is 'both' or 'deserialize'"
)]
serde_skip_deserializing_primary_key: bool,
}, },
} }

View File

@ -27,6 +27,7 @@ pub async fn run_generate_command(
with_copy_enums, with_copy_enums,
date_time_crate, date_time_crate,
lib, lib,
serde_skip_deserializing_primary_key,
} => { } => {
if verbose { if verbose {
let _ = tracing_subscriber::fmt() let _ = tracing_subscriber::fmt()
@ -164,6 +165,7 @@ pub async fn run_generate_command(
date_time_crate.into(), date_time_crate.into(),
schema_name, schema_name,
lib, lib,
serde_skip_deserializing_primary_key,
); );
let output = EntityTransformer::transform(table_stmts)?.generate(&writer_context); let output = EntityTransformer::transform(table_stmts)?.generate(&writer_context);

View File

@ -166,6 +166,22 @@ impl Entity {
// if exist, return nothing // if exist, return nothing
.map_or(quote! {, Eq}, |_| quote! {}) .map_or(quote! {, Eq}, |_| quote! {})
} }
pub fn get_serde_skip_deserializing(
&self,
serde_skip_deserializing_primary_key: bool,
) -> Vec<TokenStream> {
self.columns
.iter()
.map(|col| {
let is_primary_key = self.primary_keys.iter().any(|pk| pk.name == col.name);
col.get_serde_skip_deserializing(
is_primary_key,
serde_skip_deserializing_primary_key,
)
})
.collect()
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -211,6 +211,18 @@ impl Column {
} }
info info
} }
pub fn get_serde_skip_deserializing(
&self,
is_primary_key: bool,
serde_skip_deserializing_primary_key: bool,
) -> TokenStream {
if serde_skip_deserializing_primary_key && is_primary_key {
quote! { #[serde(skip_deserializing)] }
} else {
quote! {}
}
}
} }
impl From<ColumnDef> for Column { impl From<ColumnDef> for Column {

View File

@ -43,6 +43,7 @@ pub struct EntityWriterContext {
pub(crate) date_time_crate: DateTimeCrate, pub(crate) date_time_crate: DateTimeCrate,
pub(crate) schema_name: Option<String>, pub(crate) schema_name: Option<String>,
pub(crate) lib: bool, pub(crate) lib: bool,
pub(crate) serde_skip_deserializing_primary_key: bool,
} }
impl WithSerde { impl WithSerde {
@ -103,6 +104,7 @@ impl EntityWriterContext {
date_time_crate: DateTimeCrate, date_time_crate: DateTimeCrate,
schema_name: Option<String>, schema_name: Option<String>,
lib: bool, lib: bool,
serde_skip_deserializing_primary_key: bool,
) -> Self { ) -> Self {
Self { Self {
expanded_format, expanded_format,
@ -111,6 +113,7 @@ impl EntityWriterContext {
date_time_crate, date_time_crate,
schema_name, schema_name,
lib, lib,
serde_skip_deserializing_primary_key,
} }
} }
} }
@ -139,6 +142,11 @@ impl EntityWriter {
.iter() .iter()
.map(|column| column.get_info(&context.date_time_crate)) .map(|column| column.get_info(&context.date_time_crate))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
// use must have serde enabled to use this
let serde_skip_deserializing_primary_key = context
.serde_skip_deserializing_primary_key
&& (context.with_serde == WithSerde::Both
|| context.with_serde == WithSerde::Deserialize);
info!("Generating {}", entity_file); info!("Generating {}", entity_file);
for info in column_info.iter() { for info in column_info.iter() {
@ -153,6 +161,7 @@ impl EntityWriter {
&context.with_serde, &context.with_serde,
&context.date_time_crate, &context.date_time_crate,
&context.schema_name, &context.schema_name,
serde_skip_deserializing_primary_key,
) )
} else { } else {
Self::gen_compact_code_blocks( Self::gen_compact_code_blocks(
@ -160,6 +169,7 @@ impl EntityWriter {
&context.with_serde, &context.with_serde,
&context.date_time_crate, &context.date_time_crate,
&context.schema_name, &context.schema_name,
serde_skip_deserializing_primary_key,
) )
}; };
Self::write(&mut lines, code_blocks); Self::write(&mut lines, code_blocks);
@ -259,6 +269,7 @@ impl EntityWriter {
with_serde: &WithSerde, with_serde: &WithSerde,
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
schema_name: &Option<String>, schema_name: &Option<String>,
serde_skip_deserializing_primary_key: bool,
) -> Vec<TokenStream> { ) -> Vec<TokenStream> {
let mut imports = Self::gen_import(with_serde); let mut imports = Self::gen_import(with_serde);
imports.extend(Self::gen_import_active_enum(entity)); imports.extend(Self::gen_import_active_enum(entity));
@ -266,7 +277,12 @@ impl EntityWriter {
imports, imports,
Self::gen_entity_struct(), Self::gen_entity_struct(),
Self::gen_impl_entity_name(entity, schema_name), Self::gen_impl_entity_name(entity, schema_name),
Self::gen_model_struct(entity, with_serde, date_time_crate), Self::gen_model_struct(
entity,
with_serde,
date_time_crate,
serde_skip_deserializing_primary_key,
),
Self::gen_column_enum(entity), Self::gen_column_enum(entity),
Self::gen_primary_key_enum(entity), Self::gen_primary_key_enum(entity),
Self::gen_impl_primary_key(entity, date_time_crate), Self::gen_impl_primary_key(entity, date_time_crate),
@ -285,12 +301,19 @@ impl EntityWriter {
with_serde: &WithSerde, with_serde: &WithSerde,
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
schema_name: &Option<String>, schema_name: &Option<String>,
serde_skip_deserializing_primary_key: bool,
) -> Vec<TokenStream> { ) -> Vec<TokenStream> {
let mut imports = Self::gen_import(with_serde); let mut imports = Self::gen_import(with_serde);
imports.extend(Self::gen_import_active_enum(entity)); imports.extend(Self::gen_import_active_enum(entity));
let mut code_blocks = vec![ let mut code_blocks = vec![
imports, imports,
Self::gen_compact_model_struct(entity, with_serde, date_time_crate, schema_name), Self::gen_compact_model_struct(
entity,
with_serde,
date_time_crate,
schema_name,
serde_skip_deserializing_primary_key,
),
Self::gen_compact_relation_enum(entity), Self::gen_compact_relation_enum(entity),
]; ];
code_blocks.extend(Self::gen_impl_related(entity)); code_blocks.extend(Self::gen_impl_related(entity));
@ -378,16 +401,22 @@ impl EntityWriter {
entity: &Entity, entity: &Entity,
with_serde: &WithSerde, with_serde: &WithSerde,
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
serde_skip_deserializing_primary_key: bool,
) -> TokenStream { ) -> TokenStream {
let column_names_snake_case = entity.get_column_names_snake_case(); let column_names_snake_case = entity.get_column_names_snake_case();
let column_rs_types = entity.get_column_rs_types(date_time_crate); let column_rs_types = entity.get_column_rs_types(date_time_crate);
let if_eq_needed = entity.get_eq_needed(); let if_eq_needed = entity.get_eq_needed();
let serde_skip_deserializing =
entity.get_serde_skip_deserializing(serde_skip_deserializing_primary_key);
let extra_derive = with_serde.extra_derive(); let extra_derive = with_serde.extra_derive();
quote! { quote! {
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel #if_eq_needed #extra_derive)] #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel #if_eq_needed #extra_derive)]
pub struct Model { pub struct Model {
#(pub #column_names_snake_case: #column_rs_types,)* #(
#serde_skip_deserializing
pub #column_names_snake_case: #column_rs_types,
)*
} }
} }
} }
@ -566,6 +595,7 @@ impl EntityWriter {
with_serde: &WithSerde, with_serde: &WithSerde,
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
schema_name: &Option<String>, schema_name: &Option<String>,
serde_skip_deserializing_primary_key: bool,
) -> TokenStream { ) -> TokenStream {
let table_name = entity.table_name.as_str(); let table_name = entity.table_name.as_str();
let column_names_snake_case = entity.get_column_names_snake_case(); let column_names_snake_case = entity.get_column_names_snake_case();
@ -581,11 +611,12 @@ impl EntityWriter {
.iter() .iter()
.map(|col| { .map(|col| {
let mut attrs: Punctuated<_, Comma> = Punctuated::new(); let mut attrs: Punctuated<_, Comma> = Punctuated::new();
let is_primary_key = primary_keys.contains(&col.name);
if !col.is_snake_case_name() { if !col.is_snake_case_name() {
let column_name = &col.name; let column_name = &col.name;
attrs.push(quote! { column_name = #column_name }); attrs.push(quote! { column_name = #column_name });
} }
if primary_keys.contains(&col.name) { if is_primary_key {
attrs.push(quote! { primary_key }); attrs.push(quote! { primary_key });
if !col.auto_increment { if !col.auto_increment {
attrs.push(quote! { auto_increment = false }); attrs.push(quote! { auto_increment = false });
@ -600,20 +631,25 @@ impl EntityWriter {
if col.unique { if col.unique {
attrs.push(quote! { unique }); attrs.push(quote! { unique });
} }
let mut ts = quote! {};
if !attrs.is_empty() { if !attrs.is_empty() {
let mut ts = TokenStream::new();
for (i, attr) in attrs.into_iter().enumerate() { for (i, attr) in attrs.into_iter().enumerate() {
if i > 0 { if i > 0 {
ts = quote! { #ts, }; ts = quote! { #ts, };
} }
ts = quote! { #ts #attr }; ts = quote! { #ts #attr };
} }
quote! { ts = quote! { #[sea_orm(#ts)] };
#[sea_orm(#ts)]
}
} else {
TokenStream::new()
} }
let serde_skip_deserializing = col.get_serde_skip_deserializing(
is_primary_key,
serde_skip_deserializing_primary_key,
);
ts = quote! {
#ts
#serde_skip_deserializing
};
ts
}) })
.collect(); .collect();
let schema_name = match Self::gen_schema_name(schema_name) { let schema_name = match Self::gen_schema_name(schema_name) {
@ -1260,7 +1296,8 @@ mod tests {
entity, entity,
&crate::WithSerde::None, &crate::WithSerde::None,
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&None &None,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1276,7 +1313,8 @@ mod tests {
entity, entity,
&crate::WithSerde::None, &crate::WithSerde::None,
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("public".to_owned()) &Some("public".to_owned()),
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1292,7 +1330,8 @@ mod tests {
entity, entity,
&crate::WithSerde::None, &crate::WithSerde::None,
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("schema_name".to_owned()) &Some("schema_name".to_owned()),
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1344,7 +1383,8 @@ mod tests {
entity, entity,
&crate::WithSerde::None, &crate::WithSerde::None,
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&None &None,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1360,7 +1400,8 @@ mod tests {
entity, entity,
&crate::WithSerde::None, &crate::WithSerde::None,
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("public".to_owned()) &Some("public".to_owned()),
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1376,7 +1417,8 @@ mod tests {
entity, entity,
&crate::WithSerde::None, &crate::WithSerde::None,
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("schema_name".to_owned()) &Some("schema_name".to_owned()),
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1481,11 +1523,13 @@ mod tests {
cake_entity: &Entity, cake_entity: &Entity,
entity_serde_variant: &(String, WithSerde, Option<String>), entity_serde_variant: &(String, WithSerde, Option<String>),
generator: Box< generator: Box<
dyn Fn(&Entity, &WithSerde, &DateTimeCrate, &Option<String>) -> Vec<TokenStream>, dyn Fn(&Entity, &WithSerde, &DateTimeCrate, &Option<String>, bool) -> Vec<TokenStream>,
>, >,
) -> io::Result<()> { ) -> io::Result<()> {
let mut reader = BufReader::new(entity_serde_variant.0.as_bytes()); let mut reader = BufReader::new(entity_serde_variant.0.as_bytes());
let mut lines: Vec<String> = Vec::new(); let mut lines: Vec<String> = Vec::new();
let serde_skip_deserializing_primary_key = entity_serde_variant.1 == WithSerde::Both
|| entity_serde_variant.1 == WithSerde::Deserialize;
reader.read_until(b'\n', &mut Vec::new())?; reader.read_until(b'\n', &mut Vec::new())?;
@ -1496,11 +1540,13 @@ mod tests {
} }
let content = lines.join(""); let content = lines.join("");
let expected: TokenStream = content.parse().unwrap(); let expected: TokenStream = content.parse().unwrap();
println!("{:?}", entity_serde_variant.1);
let generated = generator( let generated = generator(
cake_entity, cake_entity,
&entity_serde_variant.1, &entity_serde_variant.1,
&DateTimeCrate::Chrono, &DateTimeCrate::Chrono,
&entity_serde_variant.2, &entity_serde_variant.2,
serde_skip_deserializing_primary_key,
) )
.into_iter() .into_iter()
.fold(TokenStream::new(), |mut acc, tok| { .fold(TokenStream::new(), |mut acc, tok| {

View File

@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
#[sea_orm(table_name = "cake")] #[sea_orm(table_name = "cake")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32, pub id: i32,
#[sea_orm(column_type = "Text", nullable)] #[sea_orm(column_type = "Text", nullable)]
pub name: Option<String> , pub name: Option<String> ,

View File

@ -7,6 +7,7 @@ use serde::Deserialize;
#[sea_orm(table_name = "cake")] #[sea_orm(table_name = "cake")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32, pub id: i32,
#[sea_orm(column_type = "Text", nullable)] #[sea_orm(column_type = "Text", nullable)]
pub name: Option<String> , pub name: Option<String> ,

View File

@ -14,6 +14,7 @@ impl EntityName for Entity {
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Serialize, Deserialize)]
pub struct Model { pub struct Model {
#[serde(skip_deserializing)]
pub id: i32, pub id: i32,
pub name: Option<String> , pub name: Option<String> ,
} }

View File

@ -14,6 +14,7 @@ impl EntityName for Entity {
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Deserialize)] #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Deserialize)]
pub struct Model { pub struct Model {
#[serde(skip_deserializing)]
pub id: i32, pub id: i32,
pub name: Option<String> , pub name: Option<String> ,
} }