Cont. Add serde skip options for hidden columns to the CLI (#1320)

* Add serde skip options for hidden columns to the CLI (#1171)

* Add serde skip options for hidden columns to the CLI

* Resolve rustfmt and clippy issues

* Use SerdeDeriveOptions instead of WithSerde in tests

* Resolve upstream conflict

Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>

* [CLI] serde_skip_hidden_column

* clippy

* clippy

Co-authored-by: Jacob Trueb <jtrueb@northwestern.edu>
This commit is contained in:
Billy Chan 2022-12-20 00:43:21 +08:00 committed by GitHub
parent 3f00725ee2
commit 9282ce2ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 224 additions and 30 deletions

View File

@ -200,6 +200,21 @@ pub enum GenerateSubcommands {
)] )]
with_serde: String, 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( #[clap(
action, action,
long, long,
@ -228,13 +243,6 @@ 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

@ -24,10 +24,11 @@ pub async fn run_generate_command(
database_schema, database_schema,
database_url, database_url,
with_serde, with_serde,
serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
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()
@ -160,12 +161,13 @@ pub async fn run_generate_command(
let writer_context = EntityWriterContext::new( let writer_context = EntityWriterContext::new(
expanded_format, expanded_format,
WithSerde::from_str(&with_serde).unwrap(), WithSerde::from_str(&with_serde).expect("Invalid serde derive option"),
with_copy_enums, with_copy_enums,
date_time_crate.into(), date_time_crate.into(),
schema_name, schema_name,
lib, lib,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
); );
let output = EntityTransformer::transform(table_stmts)?.generate(&writer_context); let output = EntityTransformer::transform(table_stmts)?.generate(&writer_context);

View File

@ -167,17 +167,19 @@ impl Entity {
.map_or(quote! {, Eq}, |_| quote! {}) .map_or(quote! {, Eq}, |_| quote! {})
} }
pub fn get_serde_skip_deserializing( pub fn get_column_serde_attributes(
&self, &self,
serde_skip_deserializing_primary_key: bool, serde_skip_deserializing_primary_key: bool,
serde_skip_hidden_column: bool,
) -> Vec<TokenStream> { ) -> Vec<TokenStream> {
self.columns self.columns
.iter() .iter()
.map(|col| { .map(|col| {
let is_primary_key = self.primary_keys.iter().any(|pk| pk.name == col.name); 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, is_primary_key,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
) )
}) })
.collect() .collect()

View File

@ -212,13 +212,20 @@ impl Column {
info info
} }
pub fn get_serde_skip_deserializing( pub fn get_serde_attribute(
&self, &self,
is_primary_key: bool, is_primary_key: bool,
serde_skip_deserializing_primary_key: bool, serde_skip_deserializing_primary_key: bool,
serde_skip_hidden_column: bool,
) -> TokenStream { ) -> TokenStream {
if serde_skip_deserializing_primary_key && is_primary_key { if self.name.starts_with('_') && serde_skip_hidden_column {
quote! { #[serde(skip_deserializing)] } quote! {
#[serde(skip)]
}
} else if serde_skip_deserializing_primary_key && is_primary_key {
quote! {
#[serde(skip_deserializing)]
}
} else { } else {
quote! {} quote! {}
} }

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_hidden_column: bool,
pub(crate) serde_skip_deserializing_primary_key: bool, pub(crate) serde_skip_deserializing_primary_key: bool,
} }
@ -68,11 +69,9 @@ impl WithSerde {
} }
} }
}; };
if !extra_derive.is_empty() { if !extra_derive.is_empty() {
extra_derive = quote! { , #extra_derive } extra_derive = quote! { , #extra_derive }
} }
extra_derive extra_derive
} }
} }
@ -97,6 +96,7 @@ impl FromStr for WithSerde {
} }
impl EntityWriterContext { impl EntityWriterContext {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
expanded_format: bool, expanded_format: bool,
with_serde: WithSerde, with_serde: WithSerde,
@ -105,6 +105,7 @@ impl EntityWriterContext {
schema_name: Option<String>, schema_name: Option<String>,
lib: bool, lib: bool,
serde_skip_deserializing_primary_key: bool, serde_skip_deserializing_primary_key: bool,
serde_skip_hidden_column: bool,
) -> Self { ) -> Self {
Self { Self {
expanded_format, expanded_format,
@ -114,6 +115,7 @@ impl EntityWriterContext {
schema_name, schema_name,
lib, lib,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
} }
} }
} }
@ -142,11 +144,15 @@ 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 // Serde must be enabled to use this
let serde_skip_deserializing_primary_key = context let serde_skip_deserializing_primary_key = context
.serde_skip_deserializing_primary_key .serde_skip_deserializing_primary_key
&& (context.with_serde == WithSerde::Both && matches!(context.with_serde, WithSerde::Both | WithSerde::Deserialize);
|| context.with_serde == 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); info!("Generating {}", entity_file);
for info in column_info.iter() { for info in column_info.iter() {
@ -162,6 +168,7 @@ impl EntityWriter {
&context.date_time_crate, &context.date_time_crate,
&context.schema_name, &context.schema_name,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
) )
} else { } else {
Self::gen_compact_code_blocks( Self::gen_compact_code_blocks(
@ -170,6 +177,7 @@ impl EntityWriter {
&context.date_time_crate, &context.date_time_crate,
&context.schema_name, &context.schema_name,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
) )
}; };
Self::write(&mut lines, code_blocks); Self::write(&mut lines, code_blocks);
@ -270,6 +278,7 @@ impl EntityWriter {
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
schema_name: &Option<String>, schema_name: &Option<String>,
serde_skip_deserializing_primary_key: bool, serde_skip_deserializing_primary_key: bool,
serde_skip_hidden_column: 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));
@ -282,6 +291,7 @@ impl EntityWriter {
with_serde, with_serde,
date_time_crate, date_time_crate,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
), ),
Self::gen_column_enum(entity), Self::gen_column_enum(entity),
Self::gen_primary_key_enum(entity), Self::gen_primary_key_enum(entity),
@ -302,6 +312,7 @@ impl EntityWriter {
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
schema_name: &Option<String>, schema_name: &Option<String>,
serde_skip_deserializing_primary_key: bool, serde_skip_deserializing_primary_key: bool,
serde_skip_hidden_column: 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));
@ -313,6 +324,7 @@ impl EntityWriter {
date_time_crate, date_time_crate,
schema_name, schema_name,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
), ),
Self::gen_compact_relation_enum(entity), Self::gen_compact_relation_enum(entity),
]; ];
@ -335,14 +347,12 @@ impl EntityWriter {
use serde::Serialize; use serde::Serialize;
} }
} }
WithSerde::Deserialize => { WithSerde::Deserialize => {
quote! { quote! {
#prelude_import #prelude_import
use serde::Deserialize; use serde::Deserialize;
} }
} }
WithSerde::Both => { WithSerde::Both => {
quote! { quote! {
#prelude_import #prelude_import
@ -402,19 +412,22 @@ impl EntityWriter {
with_serde: &WithSerde, with_serde: &WithSerde,
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
serde_skip_deserializing_primary_key: bool, serde_skip_deserializing_primary_key: bool,
serde_skip_hidden_column: 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 = let serde_attributes = entity.get_column_serde_attributes(
entity.get_serde_skip_deserializing(serde_skip_deserializing_primary_key); serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
);
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 {
#( #(
#serde_skip_deserializing #serde_attributes
pub #column_names_snake_case: #column_rs_types, pub #column_names_snake_case: #column_rs_types,
)* )*
} }
@ -596,6 +609,7 @@ impl EntityWriter {
date_time_crate: &DateTimeCrate, date_time_crate: &DateTimeCrate,
schema_name: &Option<String>, schema_name: &Option<String>,
serde_skip_deserializing_primary_key: bool, serde_skip_deserializing_primary_key: bool,
serde_skip_hidden_column: 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();
@ -641,13 +655,14 @@ impl EntityWriter {
} }
ts = quote! { #[sea_orm(#ts)] }; ts = quote! { #[sea_orm(#ts)] };
} }
let serde_skip_deserializing = col.get_serde_skip_deserializing( let serde_attribute = col.get_serde_attribute(
is_primary_key, is_primary_key,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
); );
ts = quote! { ts = quote! {
#ts #ts
#serde_skip_deserializing #serde_attribute
}; };
ts ts
}) })
@ -1298,6 +1313,7 @@ mod tests {
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&None, &None,
false, false,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1315,6 +1331,7 @@ mod tests {
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("public".to_owned()), &Some("public".to_owned()),
false, false,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1332,6 +1349,7 @@ mod tests {
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("schema_name".to_owned()), &Some("schema_name".to_owned()),
false, false,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1385,6 +1403,7 @@ mod tests {
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&None, &None,
false, false,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1402,6 +1421,7 @@ mod tests {
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("public".to_owned()), &Some("public".to_owned()),
false, false,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1419,6 +1439,7 @@ mod tests {
&crate::DateTimeCrate::Chrono, &crate::DateTimeCrate::Chrono,
&Some("schema_name".to_owned()), &Some("schema_name".to_owned()),
false, false,
false,
) )
.into_iter() .into_iter()
.skip(1) .skip(1)
@ -1435,7 +1456,7 @@ mod tests {
#[test] #[test]
fn test_gen_with_serde() -> io::Result<()> { 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"); assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
@ -1515,6 +1536,32 @@ mod tests {
Box::new(EntityWriter::gen_expanded_code_blocks), 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(()) Ok(())
} }
@ -1523,13 +1570,23 @@ 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>, bool) -> Vec<TokenStream>, dyn Fn(
&Entity,
&WithSerde,
&DateTimeCrate,
&Option<String>,
bool,
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 let serde_skip_deserializing_primary_key = matches!(
|| entity_serde_variant.1 == WithSerde::Deserialize; 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())?; reader.read_until(b'\n', &mut Vec::new())?;
@ -1547,6 +1604,7 @@ mod tests {
&DateTimeCrate::Chrono, &DateTimeCrate::Chrono,
&entity_serde_variant.2, &entity_serde_variant.2,
serde_skip_deserializing_primary_key, serde_skip_deserializing_primary_key,
serde_skip_hidden_column,
) )
.into_iter() .into_iter()
.fold(TokenStream::new(), |mut acc, tok| { .fold(TokenStream::new(), |mut acc, tok| {

View File

@ -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<String> ,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::fruit::Entity")]
Fruit,
}
impl Related<super::fruit::Entity> for Entity {
fn to() -> RelationDef {
Relation::Fruit.def()
}
}
impl Related<super::filling::Entity> for Entity {
fn to() -> RelationDef {
super::cake_filling::Relation::Filling.def()
}
fn via() -> Option<RelationDef> {
Some(super::cake_filling::Relation::Cake.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -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<String> ,
}
#[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<super::fruit::Entity> for Entity {
fn to() -> RelationDef {
Relation::Fruit.def()
}
}
impl Related<super::filling::Entity> for Entity {
fn to() -> RelationDef {
super::cake_filling::Relation::Filling.def()
}
fn via() -> Option<RelationDef> {
Some(super::cake_filling::Relation::Cake.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}