diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index f7983f9f0..9538d402a 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -200,6 +200,21 @@ pub enum GenerateSubcommands { )] with_serde: String, + #[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, + + #[clap( + action, + long, + default_value = "false", + help = "Opt-in to add skip attributes to hidden columns (i.e. when 'with-serde' enabled and column name starts with an underscore)" + )] + serde_skip_hidden_column: bool, + #[clap( action, long, @@ -228,13 +243,6 @@ pub enum GenerateSubcommands { help = "Generate index file as `lib.rs` instead of `mod.rs`." )] 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, }, } diff --git a/sea-orm-cli/src/commands/generate.rs b/sea-orm-cli/src/commands/generate.rs index 43756a776..587504c63 100644 --- a/sea-orm-cli/src/commands/generate.rs +++ b/sea-orm-cli/src/commands/generate.rs @@ -24,10 +24,11 @@ pub async fn run_generate_command( database_schema, database_url, with_serde, + serde_skip_deserializing_primary_key, + serde_skip_hidden_column, with_copy_enums, date_time_crate, lib, - serde_skip_deserializing_primary_key, } => { if verbose { let _ = tracing_subscriber::fmt() @@ -160,12 +161,13 @@ pub async fn run_generate_command( let writer_context = EntityWriterContext::new( expanded_format, - WithSerde::from_str(&with_serde).unwrap(), + WithSerde::from_str(&with_serde).expect("Invalid serde derive option"), with_copy_enums, date_time_crate.into(), schema_name, lib, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ); let output = EntityTransformer::transform(table_stmts)?.generate(&writer_context); diff --git a/sea-orm-codegen/src/entity/base_entity.rs b/sea-orm-codegen/src/entity/base_entity.rs index 403657ba5..7a79f6000 100644 --- a/sea-orm-codegen/src/entity/base_entity.rs +++ b/sea-orm-codegen/src/entity/base_entity.rs @@ -167,17 +167,19 @@ impl Entity { .map_or(quote! {, Eq}, |_| quote! {}) } - pub fn get_serde_skip_deserializing( + pub fn get_column_serde_attributes( &self, serde_skip_deserializing_primary_key: bool, + serde_skip_hidden_column: bool, ) -> Vec { self.columns .iter() .map(|col| { let is_primary_key = self.primary_keys.iter().any(|pk| pk.name == col.name); - col.get_serde_skip_deserializing( + col.get_serde_attribute( is_primary_key, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ) }) .collect() diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index 73d57283b..5e2a6f57f 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -212,13 +212,20 @@ impl Column { info } - pub fn get_serde_skip_deserializing( + pub fn get_serde_attribute( &self, is_primary_key: bool, serde_skip_deserializing_primary_key: bool, + serde_skip_hidden_column: bool, ) -> TokenStream { - if serde_skip_deserializing_primary_key && is_primary_key { - quote! { #[serde(skip_deserializing)] } + if self.name.starts_with('_') && serde_skip_hidden_column { + quote! { + #[serde(skip)] + } + } else if serde_skip_deserializing_primary_key && is_primary_key { + quote! { + #[serde(skip_deserializing)] + } } else { quote! {} } diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index c5de8245b..8c2ab7f38 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -43,6 +43,7 @@ pub struct EntityWriterContext { pub(crate) date_time_crate: DateTimeCrate, pub(crate) schema_name: Option, pub(crate) lib: bool, + pub(crate) serde_skip_hidden_column: bool, pub(crate) serde_skip_deserializing_primary_key: bool, } @@ -68,11 +69,9 @@ impl WithSerde { } } }; - if !extra_derive.is_empty() { extra_derive = quote! { , #extra_derive } } - extra_derive } } @@ -97,6 +96,7 @@ impl FromStr for WithSerde { } impl EntityWriterContext { + #[allow(clippy::too_many_arguments)] pub fn new( expanded_format: bool, with_serde: WithSerde, @@ -105,6 +105,7 @@ impl EntityWriterContext { schema_name: Option, lib: bool, serde_skip_deserializing_primary_key: bool, + serde_skip_hidden_column: bool, ) -> Self { Self { expanded_format, @@ -114,6 +115,7 @@ impl EntityWriterContext { schema_name, lib, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, } } } @@ -142,11 +144,15 @@ impl EntityWriter { .iter() .map(|column| column.get_info(&context.date_time_crate)) .collect::>(); - // use must have serde enabled to use this + // Serde must be 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); + && matches!(context.with_serde, WithSerde::Both | WithSerde::Deserialize); + let serde_skip_hidden_column = context.serde_skip_hidden_column + && matches!( + context.with_serde, + WithSerde::Both | WithSerde::Serialize | WithSerde::Deserialize + ); info!("Generating {}", entity_file); for info in column_info.iter() { @@ -162,6 +168,7 @@ impl EntityWriter { &context.date_time_crate, &context.schema_name, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ) } else { Self::gen_compact_code_blocks( @@ -170,6 +177,7 @@ impl EntityWriter { &context.date_time_crate, &context.schema_name, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ) }; Self::write(&mut lines, code_blocks); @@ -270,6 +278,7 @@ impl EntityWriter { date_time_crate: &DateTimeCrate, schema_name: &Option, serde_skip_deserializing_primary_key: bool, + serde_skip_hidden_column: bool, ) -> Vec { let mut imports = Self::gen_import(with_serde); imports.extend(Self::gen_import_active_enum(entity)); @@ -282,6 +291,7 @@ impl EntityWriter { with_serde, date_time_crate, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ), Self::gen_column_enum(entity), Self::gen_primary_key_enum(entity), @@ -302,6 +312,7 @@ impl EntityWriter { date_time_crate: &DateTimeCrate, schema_name: &Option, serde_skip_deserializing_primary_key: bool, + serde_skip_hidden_column: bool, ) -> Vec { let mut imports = Self::gen_import(with_serde); imports.extend(Self::gen_import_active_enum(entity)); @@ -313,6 +324,7 @@ impl EntityWriter { date_time_crate, schema_name, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ), Self::gen_compact_relation_enum(entity), ]; @@ -335,14 +347,12 @@ impl EntityWriter { use serde::Serialize; } } - WithSerde::Deserialize => { quote! { #prelude_import use serde::Deserialize; } } - WithSerde::Both => { quote! { #prelude_import @@ -402,19 +412,22 @@ impl EntityWriter { with_serde: &WithSerde, date_time_crate: &DateTimeCrate, serde_skip_deserializing_primary_key: bool, + serde_skip_hidden_column: bool, ) -> TokenStream { let column_names_snake_case = entity.get_column_names_snake_case(); let column_rs_types = entity.get_column_rs_types(date_time_crate); let if_eq_needed = entity.get_eq_needed(); - let serde_skip_deserializing = - entity.get_serde_skip_deserializing(serde_skip_deserializing_primary_key); + let serde_attributes = entity.get_column_serde_attributes( + serde_skip_deserializing_primary_key, + serde_skip_hidden_column, + ); let extra_derive = with_serde.extra_derive(); quote! { #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel #if_eq_needed #extra_derive)] pub struct Model { #( - #serde_skip_deserializing + #serde_attributes pub #column_names_snake_case: #column_rs_types, )* } @@ -596,6 +609,7 @@ impl EntityWriter { date_time_crate: &DateTimeCrate, schema_name: &Option, serde_skip_deserializing_primary_key: bool, + serde_skip_hidden_column: bool, ) -> TokenStream { let table_name = entity.table_name.as_str(); let column_names_snake_case = entity.get_column_names_snake_case(); @@ -641,13 +655,14 @@ impl EntityWriter { } ts = quote! { #[sea_orm(#ts)] }; } - let serde_skip_deserializing = col.get_serde_skip_deserializing( + let serde_attribute = col.get_serde_attribute( is_primary_key, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ); ts = quote! { #ts - #serde_skip_deserializing + #serde_attribute }; ts }) @@ -1298,6 +1313,7 @@ mod tests { &crate::DateTimeCrate::Chrono, &None, false, + false, ) .into_iter() .skip(1) @@ -1315,6 +1331,7 @@ mod tests { &crate::DateTimeCrate::Chrono, &Some("public".to_owned()), false, + false, ) .into_iter() .skip(1) @@ -1332,6 +1349,7 @@ mod tests { &crate::DateTimeCrate::Chrono, &Some("schema_name".to_owned()), false, + false, ) .into_iter() .skip(1) @@ -1385,6 +1403,7 @@ mod tests { &crate::DateTimeCrate::Chrono, &None, false, + false, ) .into_iter() .skip(1) @@ -1402,6 +1421,7 @@ mod tests { &crate::DateTimeCrate::Chrono, &Some("public".to_owned()), false, + false, ) .into_iter() .skip(1) @@ -1419,6 +1439,7 @@ mod tests { &crate::DateTimeCrate::Chrono, &Some("schema_name".to_owned()), false, + false, ) .into_iter() .skip(1) @@ -1435,7 +1456,7 @@ mod tests { #[test] fn test_gen_with_serde() -> io::Result<()> { - let cake_entity = setup().get(0).unwrap().clone(); + let mut cake_entity = setup().get_mut(0).unwrap().clone(); assert_eq!(cake_entity.get_table_name_snake_case(), "cake"); @@ -1515,6 +1536,32 @@ mod tests { Box::new(EntityWriter::gen_expanded_code_blocks), )?; + // Make the `name` column of `cake` entity as hidden column + cake_entity.columns[1].name = "_name".into(); + + assert_serde_variant_results( + &cake_entity, + &( + include_str!("../../tests/compact_with_serde/cake_serialize_with_hidden_column.rs") + .into(), + WithSerde::Serialize, + None, + ), + Box::new(EntityWriter::gen_compact_code_blocks), + )?; + assert_serde_variant_results( + &cake_entity, + &( + include_str!( + "../../tests/expanded_with_serde/cake_serialize_with_hidden_column.rs" + ) + .into(), + WithSerde::Serialize, + None, + ), + Box::new(EntityWriter::gen_expanded_code_blocks), + )?; + Ok(()) } @@ -1523,13 +1570,23 @@ mod tests { cake_entity: &Entity, entity_serde_variant: &(String, WithSerde, Option), generator: Box< - dyn Fn(&Entity, &WithSerde, &DateTimeCrate, &Option, bool) -> Vec, + dyn Fn( + &Entity, + &WithSerde, + &DateTimeCrate, + &Option, + bool, + bool, + ) -> Vec, >, ) -> io::Result<()> { let mut reader = BufReader::new(entity_serde_variant.0.as_bytes()); let mut lines: Vec = Vec::new(); - let serde_skip_deserializing_primary_key = entity_serde_variant.1 == WithSerde::Both - || entity_serde_variant.1 == WithSerde::Deserialize; + let serde_skip_deserializing_primary_key = matches!( + entity_serde_variant.1, + WithSerde::Both | WithSerde::Deserialize + ); + let serde_skip_hidden_column = matches!(entity_serde_variant.1, WithSerde::Serialize); reader.read_until(b'\n', &mut Vec::new())?; @@ -1547,6 +1604,7 @@ mod tests { &DateTimeCrate::Chrono, &entity_serde_variant.2, serde_skip_deserializing_primary_key, + serde_skip_hidden_column, ) .into_iter() .fold(TokenStream::new(), |mut acc, tok| { diff --git a/sea-orm-codegen/tests/compact_with_serde/cake_serialize_with_hidden_column.rs b/sea-orm-codegen/tests/compact_with_serde/cake_serialize_with_hidden_column.rs new file mode 100644 index 000000000..50aa9dcbb --- /dev/null +++ b/sea-orm-codegen/tests/compact_with_serde/cake_serialize_with_hidden_column.rs @@ -0,0 +1,37 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude:: * ; +use serde::Serialize; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize)] +#[sea_orm(table_name = "cake")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_name = "_name", column_type = "Text", nullable)] + #[serde(skip)] + pub name: Option , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/expanded_with_serde/cake_serialize_with_hidden_column.rs b/sea-orm-codegen/tests/expanded_with_serde/cake_serialize_with_hidden_column.rs new file mode 100644 index 000000000..74db30853 --- /dev/null +++ b/sea-orm-codegen/tests/expanded_with_serde/cake_serialize_with_hidden_column.rs @@ -0,0 +1,80 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude:: * ; +use serde::Serialize; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cake" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Serialize)] +pub struct Model { + pub id: i32, + #[serde(skip)] + pub name: Option , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + #[sea_orm(column_name = "_name")] + Name, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::Text.def().null(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {}