* feat(sea-orm-cli): output lof about generated file name. https://github.com/SeaQL/sea-orm/issues/722 * include column names in info * cargo fmt * Update sea-orm-cli/src/commands.rs Avoid multiple initializations Co-authored-by: Billy Chan <ccw.billy.123@gmail.com> * show info per column * refactor: use write! macro * Refactoring Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>
1119 lines
39 KiB
Rust
1119 lines
39 KiB
Rust
use crate::{ActiveEnum, Entity};
|
|
use heck::CamelCase;
|
|
use proc_macro2::TokenStream;
|
|
use quote::{format_ident, quote};
|
|
use std::{collections::HashMap, str::FromStr};
|
|
use syn::{punctuated::Punctuated, token::Comma};
|
|
use tracing::info;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct EntityWriter {
|
|
pub(crate) entities: Vec<Entity>,
|
|
pub(crate) enums: HashMap<String, ActiveEnum>,
|
|
}
|
|
|
|
pub struct WriterOutput {
|
|
pub files: Vec<OutputFile>,
|
|
}
|
|
|
|
pub struct OutputFile {
|
|
pub name: String,
|
|
pub content: String,
|
|
}
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
pub enum WithSerde {
|
|
None,
|
|
Serialize,
|
|
Deserialize,
|
|
Both,
|
|
}
|
|
|
|
impl WithSerde {
|
|
pub fn extra_derive(&self) -> TokenStream {
|
|
let mut extra_derive = match self {
|
|
Self::None => {
|
|
quote! {}
|
|
}
|
|
Self::Serialize => {
|
|
quote! {
|
|
Serialize
|
|
}
|
|
}
|
|
Self::Deserialize => {
|
|
quote! {
|
|
Deserialize
|
|
}
|
|
}
|
|
Self::Both => {
|
|
quote! {
|
|
Serialize, Deserialize
|
|
}
|
|
}
|
|
};
|
|
|
|
if !extra_derive.is_empty() {
|
|
extra_derive = quote! { , #extra_derive }
|
|
}
|
|
|
|
extra_derive
|
|
}
|
|
}
|
|
|
|
impl FromStr for WithSerde {
|
|
type Err = crate::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Ok(match s {
|
|
"none" => Self::None,
|
|
"serialize" => Self::Serialize,
|
|
"deserialize" => Self::Deserialize,
|
|
"both" => Self::Both,
|
|
v => {
|
|
return Err(crate::Error::TransformError(format!(
|
|
"Unsupported enum variant '{}'",
|
|
v
|
|
)))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl EntityWriter {
|
|
pub fn generate(self, expanded_format: bool, with_serde: WithSerde) -> WriterOutput {
|
|
let mut files = Vec::new();
|
|
files.extend(self.write_entities(expanded_format, &with_serde));
|
|
files.push(self.write_mod());
|
|
files.push(self.write_prelude());
|
|
if !self.enums.is_empty() {
|
|
files.push(self.write_sea_orm_active_enums(&with_serde));
|
|
}
|
|
WriterOutput { files }
|
|
}
|
|
|
|
pub fn write_entities(&self, expanded_format: bool, with_serde: &WithSerde) -> Vec<OutputFile> {
|
|
self.entities
|
|
.iter()
|
|
.map(|entity| {
|
|
let entity_file = format!("{}.rs", entity.get_table_name_snake_case());
|
|
let column_info = entity
|
|
.columns
|
|
.iter()
|
|
.map(|column| column.get_info())
|
|
.collect::<Vec<String>>();
|
|
|
|
info!("Generating {}", entity_file);
|
|
for info in column_info.iter() {
|
|
info!(" > {}", info);
|
|
}
|
|
|
|
let mut lines = Vec::new();
|
|
Self::write_doc_comment(&mut lines);
|
|
let code_blocks = if expanded_format {
|
|
Self::gen_expanded_code_blocks(entity, with_serde)
|
|
} else {
|
|
Self::gen_compact_code_blocks(entity, with_serde)
|
|
};
|
|
Self::write(&mut lines, code_blocks);
|
|
OutputFile {
|
|
name: entity_file,
|
|
content: lines.join("\n\n"),
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn write_mod(&self) -> OutputFile {
|
|
let mut lines = Vec::new();
|
|
Self::write_doc_comment(&mut lines);
|
|
let code_blocks: Vec<TokenStream> = self.entities.iter().map(Self::gen_mod).collect();
|
|
Self::write(
|
|
&mut lines,
|
|
vec![quote! {
|
|
pub mod prelude;
|
|
}],
|
|
);
|
|
lines.push("".to_owned());
|
|
Self::write(&mut lines, code_blocks);
|
|
if !self.enums.is_empty() {
|
|
Self::write(
|
|
&mut lines,
|
|
vec![quote! {
|
|
pub mod sea_orm_active_enums;
|
|
}],
|
|
);
|
|
}
|
|
OutputFile {
|
|
name: "mod.rs".to_owned(),
|
|
content: lines.join("\n"),
|
|
}
|
|
}
|
|
|
|
pub fn write_prelude(&self) -> OutputFile {
|
|
let mut lines = Vec::new();
|
|
Self::write_doc_comment(&mut lines);
|
|
let code_blocks = self.entities.iter().map(Self::gen_prelude_use).collect();
|
|
Self::write(&mut lines, code_blocks);
|
|
OutputFile {
|
|
name: "prelude.rs".to_owned(),
|
|
content: lines.join("\n"),
|
|
}
|
|
}
|
|
|
|
pub fn write_sea_orm_active_enums(&self, with_serde: &WithSerde) -> OutputFile {
|
|
let mut lines = Vec::new();
|
|
Self::write_doc_comment(&mut lines);
|
|
Self::write(&mut lines, vec![Self::gen_import(with_serde)]);
|
|
lines.push("".to_owned());
|
|
let code_blocks = self
|
|
.enums
|
|
.iter()
|
|
.map(|(_, active_enum)| active_enum.impl_active_enum(with_serde))
|
|
.collect();
|
|
Self::write(&mut lines, code_blocks);
|
|
OutputFile {
|
|
name: "sea_orm_active_enums.rs".to_owned(),
|
|
content: lines.join("\n"),
|
|
}
|
|
}
|
|
|
|
pub fn write(lines: &mut Vec<String>, code_blocks: Vec<TokenStream>) {
|
|
lines.extend(
|
|
code_blocks
|
|
.into_iter()
|
|
.map(|code_block| code_block.to_string())
|
|
.collect::<Vec<_>>(),
|
|
);
|
|
}
|
|
|
|
pub fn write_doc_comment(lines: &mut Vec<String>) {
|
|
let ver = env!("CARGO_PKG_VERSION");
|
|
let comments = vec![format!(
|
|
"//! SeaORM Entity. Generated by sea-orm-codegen {}",
|
|
ver
|
|
)];
|
|
lines.extend(comments);
|
|
lines.push("".to_owned());
|
|
}
|
|
|
|
pub fn gen_expanded_code_blocks(entity: &Entity, with_serde: &WithSerde) -> Vec<TokenStream> {
|
|
let mut imports = Self::gen_import(with_serde);
|
|
imports.extend(Self::gen_import_active_enum(entity));
|
|
let mut code_blocks = vec![
|
|
imports,
|
|
Self::gen_entity_struct(),
|
|
Self::gen_impl_entity_name(entity),
|
|
Self::gen_model_struct(entity, with_serde),
|
|
Self::gen_column_enum(entity),
|
|
Self::gen_primary_key_enum(entity),
|
|
Self::gen_impl_primary_key(entity),
|
|
Self::gen_relation_enum(entity),
|
|
Self::gen_impl_column_trait(entity),
|
|
Self::gen_impl_relation_trait(entity),
|
|
];
|
|
code_blocks.extend(Self::gen_impl_related(entity));
|
|
code_blocks.extend(Self::gen_impl_conjunct_related(entity));
|
|
code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]);
|
|
code_blocks
|
|
}
|
|
|
|
pub fn gen_compact_code_blocks(entity: &Entity, with_serde: &WithSerde) -> Vec<TokenStream> {
|
|
let mut imports = Self::gen_import(with_serde);
|
|
imports.extend(Self::gen_import_active_enum(entity));
|
|
let mut code_blocks = vec![imports, Self::gen_compact_model_struct(entity, with_serde)];
|
|
let relation_defs = if entity.get_relation_enum_name().is_empty() {
|
|
vec![
|
|
Self::gen_relation_enum(entity),
|
|
Self::gen_impl_relation_trait(entity),
|
|
]
|
|
} else {
|
|
vec![Self::gen_compact_relation_enum(entity)]
|
|
};
|
|
code_blocks.extend(relation_defs);
|
|
code_blocks.extend(Self::gen_impl_related(entity));
|
|
code_blocks.extend(Self::gen_impl_conjunct_related(entity));
|
|
code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]);
|
|
code_blocks
|
|
}
|
|
|
|
pub fn gen_import(with_serde: &WithSerde) -> TokenStream {
|
|
let prelude_import = quote!(
|
|
use sea_orm::entity::prelude::*;
|
|
);
|
|
|
|
match with_serde {
|
|
WithSerde::None => prelude_import,
|
|
WithSerde::Serialize => {
|
|
quote! {
|
|
#prelude_import
|
|
use serde::Serialize;
|
|
}
|
|
}
|
|
|
|
WithSerde::Deserialize => {
|
|
quote! {
|
|
#prelude_import
|
|
use serde::Deserialize;
|
|
}
|
|
}
|
|
|
|
WithSerde::Both => {
|
|
quote! {
|
|
#prelude_import
|
|
use serde::{Deserialize,Serialize};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_entity_struct() -> TokenStream {
|
|
quote! {
|
|
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
|
pub struct Entity;
|
|
}
|
|
}
|
|
|
|
pub fn gen_impl_entity_name(entity: &Entity) -> TokenStream {
|
|
let table_name = entity.table_name.as_str();
|
|
quote! {
|
|
impl EntityName for Entity {
|
|
fn table_name(&self) -> &str {
|
|
#table_name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_import_active_enum(entity: &Entity) -> TokenStream {
|
|
entity
|
|
.columns
|
|
.iter()
|
|
.fold(TokenStream::new(), |mut ts, col| {
|
|
if let sea_query::ColumnType::Enum(enum_name, _) = &col.col_type {
|
|
let enum_name = format_ident!("{}", enum_name.to_camel_case());
|
|
ts.extend(vec![quote! {
|
|
use super::sea_orm_active_enums::#enum_name;
|
|
}]);
|
|
}
|
|
ts
|
|
})
|
|
}
|
|
|
|
pub fn gen_model_struct(entity: &Entity, with_serde: &WithSerde) -> TokenStream {
|
|
let column_names_snake_case = entity.get_column_names_snake_case();
|
|
let column_rs_types = entity.get_column_rs_types();
|
|
|
|
let extra_derive = with_serde.extra_derive();
|
|
|
|
quote! {
|
|
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel #extra_derive)]
|
|
pub struct Model {
|
|
#(pub #column_names_snake_case: #column_rs_types,)*
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_column_enum(entity: &Entity) -> TokenStream {
|
|
let column_variants = entity.columns.iter().map(|col| {
|
|
let variant = col.get_name_camel_case();
|
|
let mut variant = quote! { #variant };
|
|
if !col.is_snake_case_name() {
|
|
let column_name = &col.name;
|
|
variant = quote! {
|
|
#[sea_orm(column_name = #column_name)]
|
|
#variant
|
|
};
|
|
}
|
|
variant
|
|
});
|
|
quote! {
|
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
|
pub enum Column {
|
|
#(#column_variants,)*
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_primary_key_enum(entity: &Entity) -> TokenStream {
|
|
let primary_key_names_camel_case = entity.get_primary_key_names_camel_case();
|
|
quote! {
|
|
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
|
pub enum PrimaryKey {
|
|
#(#primary_key_names_camel_case,)*
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_impl_primary_key(entity: &Entity) -> TokenStream {
|
|
let primary_key_auto_increment = entity.get_primary_key_auto_increment();
|
|
let value_type = entity.get_primary_key_rs_type();
|
|
quote! {
|
|
impl PrimaryKeyTrait for PrimaryKey {
|
|
type ValueType = #value_type;
|
|
|
|
fn auto_increment() -> bool {
|
|
#primary_key_auto_increment
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_relation_enum(entity: &Entity) -> TokenStream {
|
|
let relation_enum_name = entity.get_relation_enum_name();
|
|
quote! {
|
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
|
pub enum Relation {
|
|
#(#relation_enum_name,)*
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_impl_column_trait(entity: &Entity) -> TokenStream {
|
|
let column_names_camel_case = entity.get_column_names_camel_case();
|
|
let column_defs = entity.get_column_defs();
|
|
quote! {
|
|
impl ColumnTrait for Column {
|
|
type EntityName = Entity;
|
|
|
|
fn def(&self) -> ColumnDef {
|
|
match self {
|
|
#(Self::#column_names_camel_case => #column_defs,)*
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_impl_relation_trait(entity: &Entity) -> TokenStream {
|
|
let relation_enum_name = entity.get_relation_enum_name();
|
|
let relation_defs = entity.get_relation_defs();
|
|
let quoted = if relation_enum_name.is_empty() {
|
|
quote! {
|
|
panic!("No RelationDef")
|
|
}
|
|
} else {
|
|
quote! {
|
|
match self {
|
|
#(Self::#relation_enum_name => #relation_defs,)*
|
|
}
|
|
}
|
|
};
|
|
quote! {
|
|
impl RelationTrait for Relation {
|
|
fn def(&self) -> RelationDef {
|
|
#quoted
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_impl_related(entity: &Entity) -> Vec<TokenStream> {
|
|
entity
|
|
.relations
|
|
.iter()
|
|
.filter(|rel| !rel.self_referencing && rel.num_suffix == 0)
|
|
.map(|rel| {
|
|
let enum_name = rel.get_enum_name();
|
|
let module_name = rel.get_module_name();
|
|
let inner = quote! {
|
|
fn to() -> RelationDef {
|
|
Relation::#enum_name.def()
|
|
}
|
|
};
|
|
if module_name.is_some() {
|
|
quote! {
|
|
impl Related<super::#module_name::Entity> for Entity { #inner }
|
|
}
|
|
} else {
|
|
quote! {
|
|
impl Related<Entity> for Entity { #inner }
|
|
}
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn gen_impl_conjunct_related(entity: &Entity) -> Vec<TokenStream> {
|
|
let table_name_camel_case = entity.get_table_name_camel_case_ident();
|
|
let via_snake_case = entity.get_conjunct_relations_via_snake_case();
|
|
let to_snake_case = entity.get_conjunct_relations_to_snake_case();
|
|
let to_camel_case = entity.get_conjunct_relations_to_camel_case();
|
|
via_snake_case
|
|
.into_iter()
|
|
.zip(to_snake_case)
|
|
.zip(to_camel_case)
|
|
.map(|((via_snake_case, to_snake_case), to_camel_case)| {
|
|
quote! {
|
|
impl Related<super::#to_snake_case::Entity> for Entity {
|
|
fn to() -> RelationDef {
|
|
super::#via_snake_case::Relation::#to_camel_case.def()
|
|
}
|
|
|
|
fn via() -> Option<RelationDef> {
|
|
Some(super::#via_snake_case::Relation::#table_name_camel_case.def().rev())
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn gen_impl_active_model_behavior() -> TokenStream {
|
|
quote! {
|
|
impl ActiveModelBehavior for ActiveModel {}
|
|
}
|
|
}
|
|
|
|
pub fn gen_mod(entity: &Entity) -> TokenStream {
|
|
let table_name_snake_case_ident = entity.get_table_name_snake_case_ident();
|
|
quote! {
|
|
pub mod #table_name_snake_case_ident;
|
|
}
|
|
}
|
|
|
|
pub fn gen_prelude_use(entity: &Entity) -> TokenStream {
|
|
let table_name_snake_case_ident = entity.get_table_name_snake_case_ident();
|
|
let table_name_camel_case_ident = entity.get_table_name_camel_case_ident();
|
|
quote! {
|
|
pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident;
|
|
}
|
|
}
|
|
|
|
pub fn gen_compact_model_struct(entity: &Entity, with_serde: &WithSerde) -> TokenStream {
|
|
let table_name = entity.table_name.as_str();
|
|
let column_names_snake_case = entity.get_column_names_snake_case();
|
|
let column_rs_types = entity.get_column_rs_types();
|
|
let primary_keys: Vec<String> = entity
|
|
.primary_keys
|
|
.iter()
|
|
.map(|pk| pk.name.clone())
|
|
.collect();
|
|
let attrs: Vec<TokenStream> = entity
|
|
.columns
|
|
.iter()
|
|
.map(|col| {
|
|
let mut attrs: Punctuated<_, Comma> = Punctuated::new();
|
|
if !col.is_snake_case_name() {
|
|
let column_name = &col.name;
|
|
attrs.push(quote! { column_name = #column_name });
|
|
}
|
|
if primary_keys.contains(&col.name) {
|
|
attrs.push(quote! { primary_key });
|
|
if !col.auto_increment {
|
|
attrs.push(quote! { auto_increment = false });
|
|
}
|
|
}
|
|
if let Some(ts) = col.get_col_type_attrs() {
|
|
attrs.extend(vec![ts]);
|
|
if !col.not_null {
|
|
attrs.push(quote! { nullable });
|
|
}
|
|
};
|
|
if col.unique {
|
|
attrs.push(quote! { unique });
|
|
}
|
|
if !attrs.is_empty() {
|
|
let mut ts = TokenStream::new();
|
|
for (i, attr) in attrs.into_iter().enumerate() {
|
|
if i > 0 {
|
|
ts = quote! { #ts, };
|
|
}
|
|
ts = quote! { #ts #attr };
|
|
}
|
|
quote! {
|
|
#[sea_orm(#ts)]
|
|
}
|
|
} else {
|
|
TokenStream::new()
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let extra_derive = with_serde.extra_derive();
|
|
|
|
quote! {
|
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel #extra_derive)]
|
|
#[sea_orm(table_name = #table_name)]
|
|
pub struct Model {
|
|
#(
|
|
#attrs
|
|
pub #column_names_snake_case: #column_rs_types,
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn gen_compact_relation_enum(entity: &Entity) -> TokenStream {
|
|
let relation_enum_name = entity.get_relation_enum_name();
|
|
let attrs = entity.get_relation_attrs();
|
|
quote! {
|
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
pub enum Relation {
|
|
#(
|
|
#attrs
|
|
#relation_enum_name,
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{
|
|
Column, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation, RelationType,
|
|
WithSerde,
|
|
};
|
|
use pretty_assertions::assert_eq;
|
|
use proc_macro2::TokenStream;
|
|
use sea_query::{ColumnType, ForeignKeyAction};
|
|
use std::io::{self, BufRead, BufReader};
|
|
|
|
fn setup() -> Vec<Entity> {
|
|
vec![
|
|
Entity {
|
|
table_name: "cake".to_owned(),
|
|
columns: vec![
|
|
Column {
|
|
name: "id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: true,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "name".to_owned(),
|
|
col_type: ColumnType::Text,
|
|
auto_increment: false,
|
|
not_null: false,
|
|
unique: false,
|
|
},
|
|
],
|
|
relations: vec![Relation {
|
|
ref_table: "fruit".to_owned(),
|
|
columns: vec![],
|
|
ref_columns: vec![],
|
|
rel_type: RelationType::HasMany,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: false,
|
|
num_suffix: 0,
|
|
}],
|
|
conjunct_relations: vec![ConjunctRelation {
|
|
via: "cake_filling".to_owned(),
|
|
to: "filling".to_owned(),
|
|
}],
|
|
primary_keys: vec![PrimaryKey {
|
|
name: "id".to_owned(),
|
|
}],
|
|
},
|
|
Entity {
|
|
table_name: "_cake_filling_".to_owned(),
|
|
columns: vec![
|
|
Column {
|
|
name: "cake_id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "filling_id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
],
|
|
relations: vec![
|
|
Relation {
|
|
ref_table: "cake".to_owned(),
|
|
columns: vec!["cake_id".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: Some(ForeignKeyAction::Cascade),
|
|
on_update: Some(ForeignKeyAction::Cascade),
|
|
self_referencing: false,
|
|
num_suffix: 0,
|
|
},
|
|
Relation {
|
|
ref_table: "filling".to_owned(),
|
|
columns: vec!["filling_id".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: Some(ForeignKeyAction::Cascade),
|
|
on_update: Some(ForeignKeyAction::Cascade),
|
|
self_referencing: false,
|
|
num_suffix: 0,
|
|
},
|
|
],
|
|
conjunct_relations: vec![],
|
|
primary_keys: vec![
|
|
PrimaryKey {
|
|
name: "cake_id".to_owned(),
|
|
},
|
|
PrimaryKey {
|
|
name: "filling_id".to_owned(),
|
|
},
|
|
],
|
|
},
|
|
Entity {
|
|
table_name: "filling".to_owned(),
|
|
columns: vec![
|
|
Column {
|
|
name: "id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: true,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "name".to_owned(),
|
|
col_type: ColumnType::String(Some(255)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
],
|
|
relations: vec![],
|
|
conjunct_relations: vec![ConjunctRelation {
|
|
via: "cake_filling".to_owned(),
|
|
to: "cake".to_owned(),
|
|
}],
|
|
primary_keys: vec![PrimaryKey {
|
|
name: "id".to_owned(),
|
|
}],
|
|
},
|
|
Entity {
|
|
table_name: "fruit".to_owned(),
|
|
columns: vec![
|
|
Column {
|
|
name: "id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: true,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "name".to_owned(),
|
|
col_type: ColumnType::String(Some(255)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "cake_id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: false,
|
|
unique: false,
|
|
},
|
|
],
|
|
relations: vec![
|
|
Relation {
|
|
ref_table: "cake".to_owned(),
|
|
columns: vec!["cake_id".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: false,
|
|
num_suffix: 0,
|
|
},
|
|
Relation {
|
|
ref_table: "vendor".to_owned(),
|
|
columns: vec![],
|
|
ref_columns: vec![],
|
|
rel_type: RelationType::HasMany,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: false,
|
|
num_suffix: 0,
|
|
},
|
|
],
|
|
conjunct_relations: vec![],
|
|
primary_keys: vec![PrimaryKey {
|
|
name: "id".to_owned(),
|
|
}],
|
|
},
|
|
Entity {
|
|
table_name: "vendor".to_owned(),
|
|
columns: vec![
|
|
Column {
|
|
name: "id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: true,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "_name_".to_owned(),
|
|
col_type: ColumnType::String(Some(255)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "fruitId".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: false,
|
|
unique: false,
|
|
},
|
|
],
|
|
relations: vec![Relation {
|
|
ref_table: "fruit".to_owned(),
|
|
columns: vec!["fruitId".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: false,
|
|
num_suffix: 0,
|
|
}],
|
|
conjunct_relations: vec![],
|
|
primary_keys: vec![PrimaryKey {
|
|
name: "id".to_owned(),
|
|
}],
|
|
},
|
|
Entity {
|
|
table_name: "rust_keyword".to_owned(),
|
|
columns: vec![
|
|
Column {
|
|
name: "id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: true,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "testing".to_owned(),
|
|
col_type: ColumnType::TinyInteger(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "rust".to_owned(),
|
|
col_type: ColumnType::TinyUnsigned(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "keywords".to_owned(),
|
|
col_type: ColumnType::SmallInteger(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "type".to_owned(),
|
|
col_type: ColumnType::SmallUnsigned(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "typeof".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "crate".to_owned(),
|
|
col_type: ColumnType::Unsigned(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "self".to_owned(),
|
|
col_type: ColumnType::BigInteger(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "self_id1".to_owned(),
|
|
col_type: ColumnType::BigUnsigned(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "self_id2".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "fruit_id1".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "fruit_id2".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
Column {
|
|
name: "cake_id".to_owned(),
|
|
col_type: ColumnType::Integer(Some(11)),
|
|
auto_increment: false,
|
|
not_null: true,
|
|
unique: false,
|
|
},
|
|
],
|
|
relations: vec![
|
|
Relation {
|
|
ref_table: "rust_keyword".to_owned(),
|
|
columns: vec!["self_id1".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: true,
|
|
num_suffix: 1,
|
|
},
|
|
Relation {
|
|
ref_table: "rust_keyword".to_owned(),
|
|
columns: vec!["self_id2".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: true,
|
|
num_suffix: 2,
|
|
},
|
|
Relation {
|
|
ref_table: "fruit".to_owned(),
|
|
columns: vec!["fruit_id1".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: false,
|
|
num_suffix: 1,
|
|
},
|
|
Relation {
|
|
ref_table: "fruit".to_owned(),
|
|
columns: vec!["fruit_id2".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: false,
|
|
num_suffix: 2,
|
|
},
|
|
Relation {
|
|
ref_table: "cake".to_owned(),
|
|
columns: vec!["cake_id".to_owned()],
|
|
ref_columns: vec!["id".to_owned()],
|
|
rel_type: RelationType::BelongsTo,
|
|
on_delete: None,
|
|
on_update: None,
|
|
self_referencing: false,
|
|
num_suffix: 0,
|
|
},
|
|
],
|
|
conjunct_relations: vec![],
|
|
primary_keys: vec![PrimaryKey {
|
|
name: "id".to_owned(),
|
|
}],
|
|
},
|
|
]
|
|
}
|
|
|
|
#[test]
|
|
fn test_gen_expanded_code_blocks() -> io::Result<()> {
|
|
let entities = setup();
|
|
const ENTITY_FILES: [&str; 6] = [
|
|
include_str!("../../tests/expanded/cake.rs"),
|
|
include_str!("../../tests/expanded/cake_filling.rs"),
|
|
include_str!("../../tests/expanded/filling.rs"),
|
|
include_str!("../../tests/expanded/fruit.rs"),
|
|
include_str!("../../tests/expanded/vendor.rs"),
|
|
include_str!("../../tests/expanded/rust_keyword.rs"),
|
|
];
|
|
|
|
assert_eq!(entities.len(), ENTITY_FILES.len());
|
|
|
|
for (i, entity) in entities.iter().enumerate() {
|
|
let mut reader = BufReader::new(ENTITY_FILES[i].as_bytes());
|
|
let mut lines: Vec<String> = Vec::new();
|
|
|
|
reader.read_until(b';', &mut Vec::new())?;
|
|
|
|
let mut line = String::new();
|
|
while reader.read_line(&mut line)? > 0 {
|
|
lines.push(line.to_owned());
|
|
line.clear();
|
|
}
|
|
let content = lines.join("");
|
|
let expected: TokenStream = content.parse().unwrap();
|
|
let generated = EntityWriter::gen_expanded_code_blocks(entity, &crate::WithSerde::None)
|
|
.into_iter()
|
|
.skip(1)
|
|
.fold(TokenStream::new(), |mut acc, tok| {
|
|
acc.extend(tok);
|
|
acc
|
|
});
|
|
assert_eq!(expected.to_string(), generated.to_string());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_gen_compact_code_blocks() -> io::Result<()> {
|
|
let entities = setup();
|
|
const ENTITY_FILES: [&str; 6] = [
|
|
include_str!("../../tests/compact/cake.rs"),
|
|
include_str!("../../tests/compact/cake_filling.rs"),
|
|
include_str!("../../tests/compact/filling.rs"),
|
|
include_str!("../../tests/compact/fruit.rs"),
|
|
include_str!("../../tests/compact/vendor.rs"),
|
|
include_str!("../../tests/compact/rust_keyword.rs"),
|
|
];
|
|
|
|
assert_eq!(entities.len(), ENTITY_FILES.len());
|
|
|
|
for (i, entity) in entities.iter().enumerate() {
|
|
let mut reader = BufReader::new(ENTITY_FILES[i].as_bytes());
|
|
let mut lines: Vec<String> = Vec::new();
|
|
|
|
reader.read_until(b';', &mut Vec::new())?;
|
|
|
|
let mut line = String::new();
|
|
while reader.read_line(&mut line)? > 0 {
|
|
lines.push(line.to_owned());
|
|
line.clear();
|
|
}
|
|
let content = lines.join("");
|
|
let expected: TokenStream = content.parse().unwrap();
|
|
let generated = EntityWriter::gen_compact_code_blocks(entity, &crate::WithSerde::None)
|
|
.into_iter()
|
|
.skip(1)
|
|
.fold(TokenStream::new(), |mut acc, tok| {
|
|
acc.extend(tok);
|
|
acc
|
|
});
|
|
assert_eq!(expected.to_string(), generated.to_string());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_gen_with_serde() -> io::Result<()> {
|
|
let cake_entity = setup().get(0).unwrap().clone();
|
|
|
|
assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
|
|
|
|
// Compact code blocks
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/compact_with_serde/cake_none.rs").into(),
|
|
WithSerde::None,
|
|
),
|
|
Box::new(EntityWriter::gen_compact_code_blocks),
|
|
)?;
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/compact_with_serde/cake_serialize.rs").into(),
|
|
WithSerde::Serialize,
|
|
),
|
|
Box::new(EntityWriter::gen_compact_code_blocks),
|
|
)?;
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/compact_with_serde/cake_deserialize.rs").into(),
|
|
WithSerde::Deserialize,
|
|
),
|
|
Box::new(EntityWriter::gen_compact_code_blocks),
|
|
)?;
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/compact_with_serde/cake_both.rs").into(),
|
|
WithSerde::Both,
|
|
),
|
|
Box::new(EntityWriter::gen_compact_code_blocks),
|
|
)?;
|
|
|
|
// Expanded code blocks
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/expanded_with_serde/cake_none.rs").into(),
|
|
WithSerde::None,
|
|
),
|
|
Box::new(EntityWriter::gen_expanded_code_blocks),
|
|
)?;
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/expanded_with_serde/cake_serialize.rs").into(),
|
|
WithSerde::Serialize,
|
|
),
|
|
Box::new(EntityWriter::gen_expanded_code_blocks),
|
|
)?;
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/expanded_with_serde/cake_deserialize.rs").into(),
|
|
WithSerde::Deserialize,
|
|
),
|
|
Box::new(EntityWriter::gen_expanded_code_blocks),
|
|
)?;
|
|
assert_serde_variant_results(
|
|
&cake_entity,
|
|
&(
|
|
include_str!("../../tests/expanded_with_serde/cake_both.rs").into(),
|
|
WithSerde::Both,
|
|
),
|
|
Box::new(EntityWriter::gen_expanded_code_blocks),
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn assert_serde_variant_results(
|
|
cake_entity: &Entity,
|
|
entity_serde_variant: &(String, WithSerde),
|
|
generator: Box<dyn Fn(&Entity, &WithSerde) -> Vec<TokenStream>>,
|
|
) -> io::Result<()> {
|
|
let mut reader = BufReader::new(entity_serde_variant.0.as_bytes());
|
|
let mut lines: Vec<String> = Vec::new();
|
|
|
|
reader.read_until(b'\n', &mut Vec::new())?;
|
|
|
|
let mut line = String::new();
|
|
while reader.read_line(&mut line)? > 0 {
|
|
lines.push(line.to_owned());
|
|
line.clear();
|
|
}
|
|
let content = lines.join("");
|
|
let expected: TokenStream = content.parse().unwrap();
|
|
let generated = generator(cake_entity, &entity_serde_variant.1)
|
|
.into_iter()
|
|
.fold(TokenStream::new(), |mut acc, tok| {
|
|
acc.extend(tok);
|
|
acc
|
|
});
|
|
|
|
assert_eq!(expected.to_string(), generated.to_string());
|
|
Ok(())
|
|
}
|
|
}
|