sea_orm_codegen/entity/
writer.rs

1use crate::{ActiveEnum, ColumnOption, Entity, util::escape_rust_keyword};
2use heck::ToUpperCamelCase;
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use std::{collections::BTreeMap, str::FromStr};
6use syn::{punctuated::Punctuated, token::Comma};
7use tracing::info;
8
9mod compact;
10mod dense;
11mod expanded;
12mod frontend;
13
14#[derive(Clone, Debug)]
15pub struct EntityWriter {
16    pub(crate) entities: Vec<Entity>,
17    pub(crate) enums: BTreeMap<String, ActiveEnum>,
18}
19
20pub struct WriterOutput {
21    pub files: Vec<OutputFile>,
22}
23
24pub struct OutputFile {
25    pub name: String,
26    pub content: String,
27}
28
29#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
30pub enum WithPrelude {
31    #[default]
32    All,
33    None,
34    AllAllowUnusedImports,
35}
36
37#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
38pub enum WithSerde {
39    #[default]
40    None,
41    Serialize,
42    Deserialize,
43    Both,
44}
45
46#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
47pub enum DateTimeCrate {
48    #[default]
49    Chrono,
50    Time,
51}
52
53#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
54pub enum BigIntegerType {
55    #[default]
56    I64,
57    I32,
58}
59
60#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
61pub enum EntityFormat {
62    #[default]
63    Compact,
64    Expanded,
65    Frontend,
66    Dense,
67}
68
69#[derive(Debug)]
70pub struct EntityWriterContext {
71    pub(crate) entity_format: EntityFormat,
72    pub(crate) with_prelude: WithPrelude,
73    pub(crate) with_serde: WithSerde,
74    pub(crate) with_copy_enums: bool,
75    pub(crate) date_time_crate: DateTimeCrate,
76    pub(crate) big_integer_type: BigIntegerType,
77    pub(crate) schema_name: Option<String>,
78    pub(crate) lib: bool,
79    pub(crate) serde_skip_hidden_column: bool,
80    pub(crate) serde_skip_deserializing_primary_key: bool,
81    pub(crate) model_extra_derives: TokenStream,
82    pub(crate) model_extra_attributes: TokenStream,
83    pub(crate) enum_extra_derives: TokenStream,
84    pub(crate) enum_extra_attributes: TokenStream,
85    pub(crate) column_extra_derives: TokenStream,
86    pub(crate) seaography: bool,
87    pub(crate) impl_active_model_behavior: bool,
88}
89
90impl WithSerde {
91    pub fn extra_derive(&self) -> TokenStream {
92        let mut extra_derive = match self {
93            Self::None => {
94                quote! {}
95            }
96            Self::Serialize => {
97                quote! {
98                    Serialize
99                }
100            }
101            Self::Deserialize => {
102                quote! {
103                    Deserialize
104                }
105            }
106            Self::Both => {
107                quote! {
108                    Serialize, Deserialize
109                }
110            }
111        };
112        if !extra_derive.is_empty() {
113            extra_derive = quote! { , #extra_derive }
114        }
115        extra_derive
116    }
117}
118
119/// Converts *_extra_derives argument to token stream
120pub(crate) fn bonus_derive<T, I>(extra_derives: I) -> TokenStream
121where
122    T: Into<String>,
123    I: IntoIterator<Item = T>,
124{
125    extra_derives.into_iter().map(Into::<String>::into).fold(
126        TokenStream::default(),
127        |acc, derive| {
128            let tokens: TokenStream = derive.parse().unwrap();
129            quote! { #acc, #tokens }
130        },
131    )
132}
133
134/// convert *_extra_attributes argument to token stream
135pub(crate) fn bonus_attributes<T, I>(attributes: I) -> TokenStream
136where
137    T: Into<String>,
138    I: IntoIterator<Item = T>,
139{
140    attributes.into_iter().map(Into::<String>::into).fold(
141        TokenStream::default(),
142        |acc, attribute| {
143            let tokens: TokenStream = attribute.parse().unwrap();
144            quote! {
145                #acc
146                #[#tokens]
147            }
148        },
149    )
150}
151
152impl FromStr for WithPrelude {
153    type Err = crate::Error;
154
155    fn from_str(s: &str) -> Result<Self, Self::Err> {
156        Ok(match s {
157            "none" => Self::None,
158            "all-allow-unused-imports" => Self::AllAllowUnusedImports,
159            "all" => Self::All,
160            v => {
161                return Err(crate::Error::TransformError(format!(
162                    "Unsupported enum variant '{v}'"
163                )));
164            }
165        })
166    }
167}
168
169impl FromStr for EntityFormat {
170    type Err = crate::Error;
171
172    fn from_str(s: &str) -> Result<Self, Self::Err> {
173        Ok(match s {
174            "compact" => Self::Compact,
175            "expanded" => Self::Expanded,
176            "frontend" => Self::Frontend,
177            "dense" => Self::Dense,
178            v => {
179                return Err(crate::Error::TransformError(format!(
180                    "Unsupported enum variant '{v}'"
181                )));
182            }
183        })
184    }
185}
186
187impl FromStr for WithSerde {
188    type Err = crate::Error;
189
190    fn from_str(s: &str) -> Result<Self, Self::Err> {
191        Ok(match s {
192            "none" => Self::None,
193            "serialize" => Self::Serialize,
194            "deserialize" => Self::Deserialize,
195            "both" => Self::Both,
196            v => {
197                return Err(crate::Error::TransformError(format!(
198                    "Unsupported enum variant '{v}'"
199                )));
200            }
201        })
202    }
203}
204
205impl EntityWriterContext {
206    #[allow(clippy::too_many_arguments)]
207    pub fn new(
208        entity_format: EntityFormat,
209        with_prelude: WithPrelude,
210        with_serde: WithSerde,
211        with_copy_enums: bool,
212        date_time_crate: DateTimeCrate,
213        big_integer_type: BigIntegerType,
214        schema_name: Option<String>,
215        lib: bool,
216        serde_skip_deserializing_primary_key: bool,
217        serde_skip_hidden_column: bool,
218        model_extra_derives: Vec<String>,
219        model_extra_attributes: Vec<String>,
220        enum_extra_derives: Vec<String>,
221        enum_extra_attributes: Vec<String>,
222        column_extra_derives: Vec<String>,
223        seaography: bool,
224        impl_active_model_behavior: bool,
225    ) -> Self {
226        Self {
227            entity_format,
228            with_prelude,
229            with_serde,
230            with_copy_enums,
231            date_time_crate,
232            big_integer_type,
233            schema_name,
234            lib,
235            serde_skip_deserializing_primary_key,
236            serde_skip_hidden_column,
237            model_extra_derives: bonus_derive(model_extra_derives),
238            model_extra_attributes: bonus_attributes(model_extra_attributes),
239            enum_extra_derives: bonus_derive(enum_extra_derives),
240            enum_extra_attributes: bonus_attributes(enum_extra_attributes),
241            column_extra_derives: bonus_derive(column_extra_derives),
242            seaography,
243            impl_active_model_behavior,
244        }
245    }
246
247    fn column_option(&self) -> ColumnOption {
248        ColumnOption {
249            date_time_crate: self.date_time_crate,
250            big_integer_type: self.big_integer_type,
251        }
252    }
253}
254
255impl EntityWriter {
256    pub fn generate(self, context: &EntityWriterContext) -> WriterOutput {
257        let mut files = Vec::new();
258        files.extend(self.write_entities(context));
259        let with_prelude = context.with_prelude != WithPrelude::None;
260        files.push(self.write_index_file(context.lib, with_prelude, context.seaography));
261        if with_prelude {
262            files.push(self.write_prelude(context.with_prelude, context.entity_format));
263        }
264        if !self.enums.is_empty() {
265            files.push(self.write_sea_orm_active_enums(
266                &context.with_serde,
267                context.with_copy_enums,
268                &context.enum_extra_derives,
269                &context.enum_extra_attributes,
270                context.entity_format,
271            ));
272        }
273        WriterOutput { files }
274    }
275
276    pub fn write_entities(&self, context: &EntityWriterContext) -> Vec<OutputFile> {
277        self.entities
278            .iter()
279            .map(|entity| {
280                let entity_file = format!("{}.rs", entity.get_table_name_snake_case());
281                let column_info = entity
282                    .columns
283                    .iter()
284                    .map(|column| column.get_info(&context.column_option()))
285                    .collect::<Vec<String>>();
286                // Serde must be enabled to use this
287                let serde_skip_deserializing_primary_key = context
288                    .serde_skip_deserializing_primary_key
289                    && matches!(context.with_serde, WithSerde::Both | WithSerde::Deserialize);
290                let serde_skip_hidden_column = context.serde_skip_hidden_column
291                    && matches!(
292                        context.with_serde,
293                        WithSerde::Both | WithSerde::Serialize | WithSerde::Deserialize
294                    );
295
296                info!("Generating {}", entity_file);
297                for info in column_info.iter() {
298                    info!("    > {}", info);
299                }
300
301                let mut lines = Vec::new();
302                Self::write_doc_comment(&mut lines);
303                let code_blocks = if context.entity_format == EntityFormat::Frontend {
304                    Self::gen_frontend_code_blocks(
305                        entity,
306                        &context.with_serde,
307                        &context.column_option(),
308                        &context.schema_name,
309                        serde_skip_deserializing_primary_key,
310                        serde_skip_hidden_column,
311                        &context.model_extra_derives,
312                        &context.model_extra_attributes,
313                        &context.column_extra_derives,
314                        context.seaography,
315                        context.impl_active_model_behavior,
316                    )
317                } else if context.entity_format == EntityFormat::Expanded {
318                    Self::gen_expanded_code_blocks(
319                        entity,
320                        &context.with_serde,
321                        &context.column_option(),
322                        &context.schema_name,
323                        serde_skip_deserializing_primary_key,
324                        serde_skip_hidden_column,
325                        &context.model_extra_derives,
326                        &context.model_extra_attributes,
327                        &context.column_extra_derives,
328                        context.seaography,
329                        context.impl_active_model_behavior,
330                    )
331                } else if context.entity_format == EntityFormat::Dense {
332                    Self::gen_dense_code_blocks(
333                        entity,
334                        &context.with_serde,
335                        &context.column_option(),
336                        &context.schema_name,
337                        serde_skip_deserializing_primary_key,
338                        serde_skip_hidden_column,
339                        &context.model_extra_derives,
340                        &context.model_extra_attributes,
341                        &context.column_extra_derives,
342                        context.seaography,
343                        context.impl_active_model_behavior,
344                    )
345                } else {
346                    Self::gen_compact_code_blocks(
347                        entity,
348                        &context.with_serde,
349                        &context.column_option(),
350                        &context.schema_name,
351                        serde_skip_deserializing_primary_key,
352                        serde_skip_hidden_column,
353                        &context.model_extra_derives,
354                        &context.model_extra_attributes,
355                        &context.column_extra_derives,
356                        context.seaography,
357                        context.impl_active_model_behavior,
358                    )
359                };
360                Self::write(&mut lines, code_blocks);
361                OutputFile {
362                    name: entity_file,
363                    content: lines.join("\n\n"),
364                }
365            })
366            .collect()
367    }
368
369    pub fn write_index_file(&self, lib: bool, prelude: bool, seaography: bool) -> OutputFile {
370        let mut lines = Vec::new();
371        Self::write_doc_comment(&mut lines);
372        let code_blocks: Vec<TokenStream> = self.entities.iter().map(Self::gen_mod).collect();
373        if prelude {
374            Self::write(
375                &mut lines,
376                vec![quote! {
377                    pub mod prelude;
378                }],
379            );
380            lines.push("".to_owned());
381        }
382        Self::write(&mut lines, code_blocks);
383        if !self.enums.is_empty() {
384            Self::write(
385                &mut lines,
386                vec![quote! {
387                    pub mod sea_orm_active_enums;
388                }],
389            );
390        }
391
392        if seaography {
393            lines.push("".to_owned());
394            let ts = Self::gen_seaography_entity_mod(&self.entities, &self.enums);
395            Self::write(&mut lines, vec![ts]);
396        }
397
398        let file_name = match lib {
399            true => "lib.rs".to_owned(),
400            false => "mod.rs".to_owned(),
401        };
402
403        OutputFile {
404            name: file_name,
405            content: lines.join("\n"),
406        }
407    }
408
409    pub fn write_prelude(
410        &self,
411        with_prelude: WithPrelude,
412        entity_format: EntityFormat,
413    ) -> OutputFile {
414        let mut lines = Vec::new();
415        Self::write_doc_comment(&mut lines);
416        if with_prelude == WithPrelude::AllAllowUnusedImports {
417            Self::write_allow_unused_imports(&mut lines)
418        }
419        let code_blocks = self
420            .entities
421            .iter()
422            .map({
423                if entity_format == EntityFormat::Frontend {
424                    Self::gen_prelude_use_model
425                } else {
426                    Self::gen_prelude_use
427                }
428            })
429            .collect();
430        Self::write(&mut lines, code_blocks);
431        OutputFile {
432            name: "prelude.rs".to_owned(),
433            content: lines.join("\n"),
434        }
435    }
436
437    pub fn write_sea_orm_active_enums(
438        &self,
439        with_serde: &WithSerde,
440        with_copy_enums: bool,
441        extra_derives: &TokenStream,
442        extra_attributes: &TokenStream,
443        entity_format: EntityFormat,
444    ) -> OutputFile {
445        let mut lines = Vec::new();
446        Self::write_doc_comment(&mut lines);
447        if entity_format == EntityFormat::Frontend {
448            Self::write(&mut lines, vec![Self::gen_import_serde(with_serde)]);
449        } else {
450            Self::write(&mut lines, vec![Self::gen_import(with_serde)]);
451        }
452        lines.push("".to_owned());
453        let code_blocks = self
454            .enums
455            .values()
456            .map(|active_enum| {
457                active_enum.impl_active_enum(
458                    with_serde,
459                    with_copy_enums,
460                    extra_derives,
461                    extra_attributes,
462                    entity_format,
463                )
464            })
465            .collect();
466        Self::write(&mut lines, code_blocks);
467        OutputFile {
468            name: "sea_orm_active_enums.rs".to_owned(),
469            content: lines.join("\n"),
470        }
471    }
472
473    pub fn write(lines: &mut Vec<String>, code_blocks: Vec<TokenStream>) {
474        lines.extend(
475            code_blocks
476                .into_iter()
477                .map(|code_block| code_block.to_string())
478                .collect::<Vec<_>>(),
479        );
480    }
481
482    pub fn write_doc_comment(lines: &mut Vec<String>) {
483        let ver = env!("CARGO_PKG_VERSION");
484        let comments = vec![format!(
485            "//! `SeaORM` Entity, @generated by sea-orm-codegen {ver}"
486        )];
487        lines.extend(comments);
488        lines.push("".to_owned());
489    }
490
491    pub fn write_allow_unused_imports(lines: &mut Vec<String>) {
492        lines.extend(vec!["#![allow(unused_imports)]".to_string()]);
493        lines.push("".to_owned());
494    }
495
496    pub fn gen_import(with_serde: &WithSerde) -> TokenStream {
497        let serde_import = Self::gen_import_serde(with_serde);
498        quote! {
499            use sea_orm::entity::prelude::*;
500            #serde_import
501        }
502    }
503
504    pub fn gen_import_serde(with_serde: &WithSerde) -> TokenStream {
505        match with_serde {
506            WithSerde::None => Default::default(),
507            WithSerde::Serialize => {
508                quote! {
509                    use serde::Serialize;
510                }
511            }
512            WithSerde::Deserialize => {
513                quote! {
514                    use serde::Deserialize;
515                }
516            }
517            WithSerde::Both => {
518                quote! {
519                    use serde::{Deserialize,Serialize};
520                }
521            }
522        }
523    }
524
525    pub fn gen_entity_struct() -> TokenStream {
526        quote! {
527            #[derive(Copy, Clone, Default, Debug, DeriveEntity)]
528            pub struct Entity;
529        }
530    }
531
532    pub fn gen_impl_entity_name(entity: &Entity, schema_name: &Option<String>) -> TokenStream {
533        let schema_name = match Self::gen_schema_name(schema_name) {
534            Some(schema_name) => quote! {
535                fn schema_name(&self) -> Option<&str> {
536                    Some(#schema_name)
537                }
538            },
539            None => quote! {},
540        };
541        let table_name = entity.table_name.as_str();
542        let table_name = quote! {
543            fn table_name(&self) -> &str {
544                #table_name
545            }
546        };
547        quote! {
548            impl EntityName for Entity {
549                #schema_name
550                #table_name
551            }
552        }
553    }
554
555    pub fn gen_import_active_enum(entity: &Entity) -> TokenStream {
556        entity
557            .columns
558            .iter()
559            .fold(
560                (TokenStream::new(), Vec::new()),
561                |(mut ts, mut enums), col| {
562                    if let sea_query::ColumnType::Enum { name, .. } = col.get_inner_col_type() {
563                        if !enums.contains(&name) {
564                            enums.push(name);
565                            let enum_name =
566                                format_ident!("{}", name.to_string().to_upper_camel_case());
567                            ts.extend([quote! {
568                                use super::sea_orm_active_enums::#enum_name;
569                            }]);
570                        }
571                    }
572                    (ts, enums)
573                },
574            )
575            .0
576    }
577
578    pub fn gen_column_enum(entity: &Entity, column_extra_derives: &TokenStream) -> TokenStream {
579        let column_variants = entity.columns.iter().map(|col| {
580            let variant = col.get_name_camel_case();
581            let mut variant = quote! { #variant };
582            if !col.is_snake_case_name() {
583                let column_name = &col.name;
584                variant = quote! {
585                    #[sea_orm(column_name = #column_name)]
586                    #variant
587                };
588            }
589            variant
590        });
591        quote! {
592            #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn #column_extra_derives)]
593            pub enum Column {
594                #(#column_variants,)*
595            }
596        }
597    }
598
599    pub fn gen_primary_key_enum(entity: &Entity) -> TokenStream {
600        let primary_key_names_camel_case = entity.get_primary_key_names_camel_case();
601        quote! {
602            #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
603            pub enum PrimaryKey {
604                #(#primary_key_names_camel_case,)*
605            }
606        }
607    }
608
609    pub fn gen_impl_primary_key(entity: &Entity, column_option: &ColumnOption) -> TokenStream {
610        let primary_key_auto_increment = entity.get_primary_key_auto_increment();
611        let value_type = entity.get_primary_key_rs_type(column_option);
612        quote! {
613            impl PrimaryKeyTrait for PrimaryKey {
614                type ValueType = #value_type;
615
616                fn auto_increment() -> bool {
617                    #primary_key_auto_increment
618                }
619            }
620        }
621    }
622
623    pub fn gen_relation_enum(entity: &Entity) -> TokenStream {
624        let relation_enum_name = entity.get_relation_enum_name();
625        quote! {
626            #[derive(Copy, Clone, Debug, EnumIter)]
627            pub enum Relation {
628                #(#relation_enum_name,)*
629            }
630        }
631    }
632
633    pub fn gen_impl_column_trait(entity: &Entity) -> TokenStream {
634        let column_names_camel_case = entity.get_column_names_camel_case();
635        let column_defs = entity.get_column_defs();
636        quote! {
637            impl ColumnTrait for Column {
638                type EntityName = Entity;
639
640                fn def(&self) -> ColumnDef {
641                    match self {
642                        #(Self::#column_names_camel_case => #column_defs,)*
643                    }
644                }
645            }
646        }
647    }
648
649    pub fn gen_impl_relation_trait(entity: &Entity) -> TokenStream {
650        let relation_enum_name = entity.get_relation_enum_name();
651        let relation_defs = entity.get_relation_defs();
652        let quoted = if relation_enum_name.is_empty() {
653            quote! {
654                panic!("No RelationDef")
655            }
656        } else {
657            quote! {
658                match self {
659                    #(Self::#relation_enum_name => #relation_defs,)*
660                }
661            }
662        };
663        quote! {
664            impl RelationTrait for Relation {
665                fn def(&self) -> RelationDef {
666                    #quoted
667                }
668            }
669        }
670    }
671
672    pub fn gen_impl_related(entity: &Entity) -> Vec<TokenStream> {
673        entity
674            .relations
675            .iter()
676            .filter(|rel| !rel.self_referencing && rel.num_suffix == 0 && rel.impl_related)
677            .map(|rel| {
678                let enum_name = rel.get_enum_name();
679                let module_name = rel.get_module_name();
680                let inner = quote! {
681                    fn to() -> RelationDef {
682                        Relation::#enum_name.def()
683                    }
684                };
685                if module_name.is_some() {
686                    quote! {
687                        impl Related<super::#module_name::Entity> for Entity { #inner }
688                    }
689                } else {
690                    quote! {
691                        impl Related<Entity> for Entity { #inner }
692                    }
693                }
694            })
695            .collect()
696    }
697
698    /// Used to generate `enum RelatedEntity` that is useful to the Seaography project
699    pub fn gen_related_entity(entity: &Entity) -> TokenStream {
700        let related_enum_name = entity.get_related_entity_enum_name();
701        let related_attrs = entity.get_related_entity_attrs();
702
703        quote! {
704            #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
705            pub enum RelatedEntity {
706                #(
707                    #related_attrs
708                    #related_enum_name
709                ),*
710            }
711        }
712    }
713
714    pub fn gen_impl_conjunct_related(entity: &Entity) -> Vec<TokenStream> {
715        let table_name_camel_case = entity.get_table_name_camel_case_ident();
716        let via_snake_case = entity.get_conjunct_relations_via_snake_case();
717        let to_snake_case = entity.get_conjunct_relations_to_snake_case();
718        let to_upper_camel_case = entity.get_conjunct_relations_to_upper_camel_case();
719        via_snake_case
720            .into_iter()
721            .zip(to_snake_case)
722            .zip(to_upper_camel_case)
723            .map(|((via_snake_case, to_snake_case), to_upper_camel_case)| {
724                quote! {
725                    impl Related<super::#to_snake_case::Entity> for Entity {
726                        fn to() -> RelationDef {
727                            super::#via_snake_case::Relation::#to_upper_camel_case.def()
728                        }
729
730                        fn via() -> Option<RelationDef> {
731                            Some(super::#via_snake_case::Relation::#table_name_camel_case.def().rev())
732                        }
733                    }
734                }
735            })
736            .collect()
737    }
738
739    pub fn impl_active_model_behavior() -> TokenStream {
740        quote! {
741            impl ActiveModelBehavior for ActiveModel {}
742        }
743    }
744
745    pub fn gen_mod(entity: &Entity) -> TokenStream {
746        let table_name_snake_case_ident = format_ident!(
747            "{}",
748            escape_rust_keyword(entity.get_table_name_snake_case_ident())
749        );
750        quote! {
751            pub mod #table_name_snake_case_ident;
752        }
753    }
754
755    pub fn gen_seaography_entity_mod(
756        entities: &[Entity],
757        enums: &BTreeMap<String, ActiveEnum>,
758    ) -> TokenStream {
759        let mut ts = TokenStream::new();
760        for entity in entities {
761            let table_name_snake_case_ident = format_ident!(
762                "{}",
763                escape_rust_keyword(entity.get_table_name_snake_case_ident())
764            );
765            ts = quote! {
766                #ts
767                #table_name_snake_case_ident,
768            }
769        }
770        ts = quote! {
771            seaography::register_entity_modules!([
772                #ts
773            ]);
774        };
775
776        let mut enum_ts = TokenStream::new();
777        for active_enum in enums.values() {
778            let enum_name = &active_enum.enum_name.to_string();
779            let enum_iden = format_ident!("{}", enum_name.to_upper_camel_case());
780            enum_ts = quote! {
781                #enum_ts
782                sea_orm_active_enums::#enum_iden,
783            }
784        }
785        if !enum_ts.is_empty() {
786            ts = quote! {
787                #ts
788
789                seaography::register_active_enums!([
790                    #enum_ts
791                ]);
792            };
793        }
794        ts
795    }
796
797    pub fn gen_prelude_use(entity: &Entity) -> TokenStream {
798        let table_name_snake_case_ident = entity.get_table_name_snake_case_ident();
799        let table_name_camel_case_ident = entity.get_table_name_camel_case_ident();
800        quote! {
801            pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident;
802        }
803    }
804
805    pub fn gen_prelude_use_model(entity: &Entity) -> TokenStream {
806        let table_name_snake_case_ident = entity.get_table_name_snake_case_ident();
807        let table_name_camel_case_ident = entity.get_table_name_camel_case_ident();
808        quote! {
809            pub use super::#table_name_snake_case_ident::Model as #table_name_camel_case_ident;
810        }
811    }
812
813    pub fn gen_schema_name(schema_name: &Option<String>) -> Option<TokenStream> {
814        schema_name
815            .as_ref()
816            .map(|schema_name| quote! { #schema_name })
817    }
818}
819
820#[cfg(test)]
821mod tests {
822    use crate::{
823        Column, ColumnOption, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation,
824        RelationType, WithSerde,
825        entity::writer::{bonus_attributes, bonus_derive},
826    };
827    use pretty_assertions::assert_eq;
828    use proc_macro2::TokenStream;
829    use quote::quote;
830    use sea_query::{Alias, ColumnType, ForeignKeyAction, RcOrArc, SeaRc, StringLen};
831    use std::io::{self, BufRead, BufReader, Read};
832
833    fn default_column_option() -> ColumnOption {
834        Default::default()
835    }
836
837    fn setup() -> Vec<Entity> {
838        vec![
839            Entity {
840                table_name: "cake".to_owned(),
841                columns: vec![
842                    Column {
843                        name: "id".to_owned(),
844                        col_type: ColumnType::Integer,
845                        auto_increment: true,
846                        not_null: true,
847                        unique: false,
848                        unique_key: None,
849                    },
850                    Column {
851                        name: "name".to_owned(),
852                        col_type: ColumnType::Text,
853                        auto_increment: false,
854                        not_null: false,
855                        unique: false,
856                        unique_key: None,
857                    },
858                ],
859                relations: vec![Relation {
860                    ref_table: "fruit".to_owned(),
861                    columns: vec![],
862                    ref_columns: vec![],
863                    rel_type: RelationType::HasMany,
864                    on_delete: None,
865                    on_update: None,
866                    self_referencing: false,
867                    num_suffix: 0,
868                    impl_related: true,
869                }],
870                conjunct_relations: vec![ConjunctRelation {
871                    via: "cake_filling".to_owned(),
872                    to: "filling".to_owned(),
873                }],
874                primary_keys: vec![PrimaryKey {
875                    name: "id".to_owned(),
876                }],
877            },
878            Entity {
879                table_name: "_cake_filling_".to_owned(),
880                columns: vec![
881                    Column {
882                        name: "cake_id".to_owned(),
883                        col_type: ColumnType::Integer,
884                        auto_increment: false,
885                        not_null: true,
886                        unique: false,
887                        unique_key: None,
888                    },
889                    Column {
890                        name: "filling_id".to_owned(),
891                        col_type: ColumnType::Integer,
892                        auto_increment: false,
893                        not_null: true,
894                        unique: false,
895                        unique_key: None,
896                    },
897                ],
898                relations: vec![
899                    Relation {
900                        ref_table: "cake".to_owned(),
901                        columns: vec!["cake_id".to_owned()],
902                        ref_columns: vec!["id".to_owned()],
903                        rel_type: RelationType::BelongsTo,
904                        on_delete: Some(ForeignKeyAction::Cascade),
905                        on_update: Some(ForeignKeyAction::Cascade),
906                        self_referencing: false,
907                        num_suffix: 0,
908                        impl_related: true,
909                    },
910                    Relation {
911                        ref_table: "filling".to_owned(),
912                        columns: vec!["filling_id".to_owned()],
913                        ref_columns: vec!["id".to_owned()],
914                        rel_type: RelationType::BelongsTo,
915                        on_delete: Some(ForeignKeyAction::Cascade),
916                        on_update: Some(ForeignKeyAction::Cascade),
917                        self_referencing: false,
918                        num_suffix: 0,
919                        impl_related: true,
920                    },
921                ],
922                conjunct_relations: vec![],
923                primary_keys: vec![
924                    PrimaryKey {
925                        name: "cake_id".to_owned(),
926                    },
927                    PrimaryKey {
928                        name: "filling_id".to_owned(),
929                    },
930                ],
931            },
932            Entity {
933                table_name: "cake_filling_price".to_owned(),
934                columns: vec![
935                    Column {
936                        name: "cake_id".to_owned(),
937                        col_type: ColumnType::Integer,
938                        auto_increment: false,
939                        not_null: true,
940                        unique: false,
941                        unique_key: None,
942                    },
943                    Column {
944                        name: "filling_id".to_owned(),
945                        col_type: ColumnType::Integer,
946                        auto_increment: false,
947                        not_null: true,
948                        unique: false,
949                        unique_key: None,
950                    },
951                    Column {
952                        name: "price".to_owned(),
953                        col_type: ColumnType::Decimal(None),
954                        auto_increment: false,
955                        not_null: true,
956                        unique: false,
957                        unique_key: None,
958                    },
959                ],
960                relations: vec![Relation {
961                    ref_table: "cake_filling".to_owned(),
962                    columns: vec!["cake_id".to_owned(), "filling_id".to_owned()],
963                    ref_columns: vec!["cake_id".to_owned(), "filling_id".to_owned()],
964                    rel_type: RelationType::BelongsTo,
965                    on_delete: None,
966                    on_update: None,
967                    self_referencing: false,
968                    num_suffix: 0,
969                    impl_related: true,
970                }],
971                conjunct_relations: vec![],
972                primary_keys: vec![
973                    PrimaryKey {
974                        name: "cake_id".to_owned(),
975                    },
976                    PrimaryKey {
977                        name: "filling_id".to_owned(),
978                    },
979                ],
980            },
981            Entity {
982                table_name: "filling".to_owned(),
983                columns: vec![
984                    Column {
985                        name: "id".to_owned(),
986                        col_type: ColumnType::Integer,
987                        auto_increment: true,
988                        not_null: true,
989                        unique: false,
990                        unique_key: None,
991                    },
992                    Column {
993                        name: "name".to_owned(),
994                        col_type: ColumnType::String(StringLen::N(255)),
995                        auto_increment: false,
996                        not_null: true,
997                        unique: false,
998                        unique_key: None,
999                    },
1000                ],
1001                relations: vec![],
1002                conjunct_relations: vec![ConjunctRelation {
1003                    via: "cake_filling".to_owned(),
1004                    to: "cake".to_owned(),
1005                }],
1006                primary_keys: vec![PrimaryKey {
1007                    name: "id".to_owned(),
1008                }],
1009            },
1010            Entity {
1011                table_name: "fruit".to_owned(),
1012                columns: vec![
1013                    Column {
1014                        name: "id".to_owned(),
1015                        col_type: ColumnType::Integer,
1016                        auto_increment: true,
1017                        not_null: true,
1018                        unique: false,
1019                        unique_key: None,
1020                    },
1021                    Column {
1022                        name: "name".to_owned(),
1023                        col_type: ColumnType::String(StringLen::N(255)),
1024                        auto_increment: false,
1025                        not_null: true,
1026                        unique: false,
1027                        unique_key: None,
1028                    },
1029                    Column {
1030                        name: "cake_id".to_owned(),
1031                        col_type: ColumnType::Integer,
1032                        auto_increment: false,
1033                        not_null: false,
1034                        unique: false,
1035                        unique_key: None,
1036                    },
1037                ],
1038                relations: vec![
1039                    Relation {
1040                        ref_table: "cake".to_owned(),
1041                        columns: vec!["cake_id".to_owned()],
1042                        ref_columns: vec!["id".to_owned()],
1043                        rel_type: RelationType::BelongsTo,
1044                        on_delete: None,
1045                        on_update: None,
1046                        self_referencing: false,
1047                        num_suffix: 0,
1048                        impl_related: true,
1049                    },
1050                    Relation {
1051                        ref_table: "vendor".to_owned(),
1052                        columns: vec![],
1053                        ref_columns: vec![],
1054                        rel_type: RelationType::HasMany,
1055                        on_delete: None,
1056                        on_update: None,
1057                        self_referencing: false,
1058                        num_suffix: 0,
1059                        impl_related: true,
1060                    },
1061                ],
1062                conjunct_relations: vec![],
1063                primary_keys: vec![PrimaryKey {
1064                    name: "id".to_owned(),
1065                }],
1066            },
1067            Entity {
1068                table_name: "vendor".to_owned(),
1069                columns: vec![
1070                    Column {
1071                        name: "id".to_owned(),
1072                        col_type: ColumnType::Integer,
1073                        auto_increment: true,
1074                        not_null: true,
1075                        unique: false,
1076                        unique_key: None,
1077                    },
1078                    Column {
1079                        name: "_name_".to_owned(),
1080                        col_type: ColumnType::String(StringLen::N(255)),
1081                        auto_increment: false,
1082                        not_null: true,
1083                        unique: false,
1084                        unique_key: None,
1085                    },
1086                    Column {
1087                        name: "fruitId".to_owned(),
1088                        col_type: ColumnType::Integer,
1089                        auto_increment: false,
1090                        not_null: false,
1091                        unique: false,
1092                        unique_key: None,
1093                    },
1094                ],
1095                relations: vec![Relation {
1096                    ref_table: "fruit".to_owned(),
1097                    columns: vec!["fruitId".to_owned()],
1098                    ref_columns: vec!["id".to_owned()],
1099                    rel_type: RelationType::BelongsTo,
1100                    on_delete: None,
1101                    on_update: None,
1102                    self_referencing: false,
1103                    num_suffix: 0,
1104                    impl_related: true,
1105                }],
1106                conjunct_relations: vec![],
1107                primary_keys: vec![PrimaryKey {
1108                    name: "id".to_owned(),
1109                }],
1110            },
1111            Entity {
1112                table_name: "rust_keyword".to_owned(),
1113                columns: vec![
1114                    Column {
1115                        name: "id".to_owned(),
1116                        col_type: ColumnType::Integer,
1117                        auto_increment: true,
1118                        not_null: true,
1119                        unique: false,
1120                        unique_key: None,
1121                    },
1122                    Column {
1123                        name: "testing".to_owned(),
1124                        col_type: ColumnType::TinyInteger,
1125                        auto_increment: false,
1126                        not_null: true,
1127                        unique: false,
1128                        unique_key: None,
1129                    },
1130                    Column {
1131                        name: "rust".to_owned(),
1132                        col_type: ColumnType::TinyUnsigned,
1133                        auto_increment: false,
1134                        not_null: true,
1135                        unique: false,
1136                        unique_key: None,
1137                    },
1138                    Column {
1139                        name: "keywords".to_owned(),
1140                        col_type: ColumnType::SmallInteger,
1141                        auto_increment: false,
1142                        not_null: true,
1143                        unique: false,
1144                        unique_key: None,
1145                    },
1146                    Column {
1147                        name: "type".to_owned(),
1148                        col_type: ColumnType::SmallUnsigned,
1149                        auto_increment: false,
1150                        not_null: true,
1151                        unique: false,
1152                        unique_key: None,
1153                    },
1154                    Column {
1155                        name: "typeof".to_owned(),
1156                        col_type: ColumnType::Integer,
1157                        auto_increment: false,
1158                        not_null: true,
1159                        unique: false,
1160                        unique_key: None,
1161                    },
1162                    Column {
1163                        name: "crate".to_owned(),
1164                        col_type: ColumnType::Unsigned,
1165                        auto_increment: false,
1166                        not_null: true,
1167                        unique: false,
1168                        unique_key: None,
1169                    },
1170                    Column {
1171                        name: "self".to_owned(),
1172                        col_type: ColumnType::BigInteger,
1173                        auto_increment: false,
1174                        not_null: true,
1175                        unique: false,
1176                        unique_key: None,
1177                    },
1178                    Column {
1179                        name: "self_id1".to_owned(),
1180                        col_type: ColumnType::BigUnsigned,
1181                        auto_increment: false,
1182                        not_null: true,
1183                        unique: false,
1184                        unique_key: None,
1185                    },
1186                    Column {
1187                        name: "self_id2".to_owned(),
1188                        col_type: ColumnType::Integer,
1189                        auto_increment: false,
1190                        not_null: true,
1191                        unique: false,
1192                        unique_key: None,
1193                    },
1194                    Column {
1195                        name: "fruit_id1".to_owned(),
1196                        col_type: ColumnType::Integer,
1197                        auto_increment: false,
1198                        not_null: true,
1199                        unique: false,
1200                        unique_key: None,
1201                    },
1202                    Column {
1203                        name: "fruit_id2".to_owned(),
1204                        col_type: ColumnType::Integer,
1205                        auto_increment: false,
1206                        not_null: true,
1207                        unique: false,
1208                        unique_key: None,
1209                    },
1210                    Column {
1211                        name: "cake_id".to_owned(),
1212                        col_type: ColumnType::Integer,
1213                        auto_increment: false,
1214                        not_null: true,
1215                        unique: false,
1216                        unique_key: None,
1217                    },
1218                ],
1219                relations: vec![
1220                    Relation {
1221                        ref_table: "rust_keyword".to_owned(),
1222                        columns: vec!["self_id1".to_owned()],
1223                        ref_columns: vec!["id".to_owned()],
1224                        rel_type: RelationType::BelongsTo,
1225                        on_delete: None,
1226                        on_update: None,
1227                        self_referencing: true,
1228                        num_suffix: 1,
1229                        impl_related: true,
1230                    },
1231                    Relation {
1232                        ref_table: "rust_keyword".to_owned(),
1233                        columns: vec!["self_id2".to_owned()],
1234                        ref_columns: vec!["id".to_owned()],
1235                        rel_type: RelationType::BelongsTo,
1236                        on_delete: None,
1237                        on_update: None,
1238                        self_referencing: true,
1239                        num_suffix: 2,
1240                        impl_related: true,
1241                    },
1242                    Relation {
1243                        ref_table: "fruit".to_owned(),
1244                        columns: vec!["fruit_id1".to_owned()],
1245                        ref_columns: vec!["id".to_owned()],
1246                        rel_type: RelationType::BelongsTo,
1247                        on_delete: None,
1248                        on_update: None,
1249                        self_referencing: false,
1250                        num_suffix: 1,
1251                        impl_related: true,
1252                    },
1253                    Relation {
1254                        ref_table: "fruit".to_owned(),
1255                        columns: vec!["fruit_id2".to_owned()],
1256                        ref_columns: vec!["id".to_owned()],
1257                        rel_type: RelationType::BelongsTo,
1258                        on_delete: None,
1259                        on_update: None,
1260                        self_referencing: false,
1261                        num_suffix: 2,
1262                        impl_related: true,
1263                    },
1264                    Relation {
1265                        ref_table: "cake".to_owned(),
1266                        columns: vec!["cake_id".to_owned()],
1267                        ref_columns: vec!["id".to_owned()],
1268                        rel_type: RelationType::BelongsTo,
1269                        on_delete: None,
1270                        on_update: None,
1271                        self_referencing: false,
1272                        num_suffix: 0,
1273                        impl_related: true,
1274                    },
1275                ],
1276                conjunct_relations: vec![],
1277                primary_keys: vec![PrimaryKey {
1278                    name: "id".to_owned(),
1279                }],
1280            },
1281            Entity {
1282                table_name: "cake_with_float".to_owned(),
1283                columns: vec![
1284                    Column {
1285                        name: "id".to_owned(),
1286                        col_type: ColumnType::Integer,
1287                        auto_increment: true,
1288                        not_null: true,
1289                        unique: false,
1290                        unique_key: None,
1291                    },
1292                    Column {
1293                        name: "name".to_owned(),
1294                        col_type: ColumnType::Text,
1295                        auto_increment: false,
1296                        not_null: false,
1297                        unique: false,
1298                        unique_key: None,
1299                    },
1300                    Column {
1301                        name: "price".to_owned(),
1302                        col_type: ColumnType::Float,
1303                        auto_increment: false,
1304                        not_null: false,
1305                        unique: false,
1306                        unique_key: None,
1307                    },
1308                ],
1309                relations: vec![Relation {
1310                    ref_table: "fruit".to_owned(),
1311                    columns: vec![],
1312                    ref_columns: vec![],
1313                    rel_type: RelationType::HasMany,
1314                    on_delete: None,
1315                    on_update: None,
1316                    self_referencing: false,
1317                    num_suffix: 0,
1318                    impl_related: true,
1319                }],
1320                conjunct_relations: vec![ConjunctRelation {
1321                    via: "cake_filling".to_owned(),
1322                    to: "filling".to_owned(),
1323                }],
1324                primary_keys: vec![PrimaryKey {
1325                    name: "id".to_owned(),
1326                }],
1327            },
1328            Entity {
1329                table_name: "cake_with_double".to_owned(),
1330                columns: vec![
1331                    Column {
1332                        name: "id".to_owned(),
1333                        col_type: ColumnType::Integer,
1334                        auto_increment: true,
1335                        not_null: true,
1336                        unique: false,
1337                        unique_key: None,
1338                    },
1339                    Column {
1340                        name: "name".to_owned(),
1341                        col_type: ColumnType::Text,
1342                        auto_increment: false,
1343                        not_null: false,
1344                        unique: false,
1345                        unique_key: None,
1346                    },
1347                    Column {
1348                        name: "price".to_owned(),
1349                        col_type: ColumnType::Double,
1350                        auto_increment: false,
1351                        not_null: false,
1352                        unique: false,
1353                        unique_key: None,
1354                    },
1355                ],
1356                relations: vec![Relation {
1357                    ref_table: "fruit".to_owned(),
1358                    columns: vec![],
1359                    ref_columns: vec![],
1360                    rel_type: RelationType::HasMany,
1361                    on_delete: None,
1362                    on_update: None,
1363                    self_referencing: false,
1364                    num_suffix: 0,
1365                    impl_related: true,
1366                }],
1367                conjunct_relations: vec![ConjunctRelation {
1368                    via: "cake_filling".to_owned(),
1369                    to: "filling".to_owned(),
1370                }],
1371                primary_keys: vec![PrimaryKey {
1372                    name: "id".to_owned(),
1373                }],
1374            },
1375            Entity {
1376                table_name: "collection".to_owned(),
1377                columns: vec![
1378                    Column {
1379                        name: "id".to_owned(),
1380                        col_type: ColumnType::Integer,
1381                        auto_increment: true,
1382                        not_null: true,
1383                        unique: false,
1384                        unique_key: None,
1385                    },
1386                    Column {
1387                        name: "integers".to_owned(),
1388                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Integer)),
1389                        auto_increment: false,
1390                        not_null: true,
1391                        unique: false,
1392                        unique_key: None,
1393                    },
1394                    Column {
1395                        name: "integers_opt".to_owned(),
1396                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Integer)),
1397                        auto_increment: false,
1398                        not_null: false,
1399                        unique: false,
1400                        unique_key: None,
1401                    },
1402                ],
1403                relations: vec![],
1404                conjunct_relations: vec![],
1405                primary_keys: vec![PrimaryKey {
1406                    name: "id".to_owned(),
1407                }],
1408            },
1409            Entity {
1410                table_name: "collection_float".to_owned(),
1411                columns: vec![
1412                    Column {
1413                        name: "id".to_owned(),
1414                        col_type: ColumnType::Integer,
1415                        auto_increment: true,
1416                        not_null: true,
1417                        unique: false,
1418                        unique_key: None,
1419                    },
1420                    Column {
1421                        name: "floats".to_owned(),
1422                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Float)),
1423                        auto_increment: false,
1424                        not_null: true,
1425                        unique: false,
1426                        unique_key: None,
1427                    },
1428                    Column {
1429                        name: "doubles".to_owned(),
1430                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Double)),
1431                        auto_increment: false,
1432                        not_null: true,
1433                        unique: false,
1434                        unique_key: None,
1435                    },
1436                ],
1437                relations: vec![],
1438                conjunct_relations: vec![],
1439                primary_keys: vec![PrimaryKey {
1440                    name: "id".to_owned(),
1441                }],
1442            },
1443            Entity {
1444                table_name: "parent".to_owned(),
1445                columns: vec![
1446                    Column {
1447                        name: "id1".to_owned(),
1448                        col_type: ColumnType::Integer,
1449                        auto_increment: false,
1450                        not_null: true,
1451                        unique: false,
1452                        unique_key: None,
1453                    },
1454                    Column {
1455                        name: "id2".to_owned(),
1456                        col_type: ColumnType::Integer,
1457                        auto_increment: false,
1458                        not_null: true,
1459                        unique: false,
1460                        unique_key: None,
1461                    },
1462                ],
1463                relations: vec![Relation {
1464                    ref_table: "child".to_owned(),
1465                    columns: vec![],
1466                    ref_columns: vec![],
1467                    rel_type: RelationType::HasMany,
1468                    on_delete: None,
1469                    on_update: None,
1470                    self_referencing: false,
1471                    num_suffix: 0,
1472                    impl_related: true,
1473                }],
1474                conjunct_relations: vec![],
1475                primary_keys: vec![
1476                    PrimaryKey {
1477                        name: "id1".to_owned(),
1478                    },
1479                    PrimaryKey {
1480                        name: "id2".to_owned(),
1481                    },
1482                ],
1483            },
1484            Entity {
1485                table_name: "child".to_owned(),
1486                columns: vec![
1487                    Column {
1488                        name: "id".to_owned(),
1489                        col_type: ColumnType::Integer,
1490                        auto_increment: true,
1491                        not_null: true,
1492                        unique: false,
1493                        unique_key: None,
1494                    },
1495                    Column {
1496                        name: "parent_id1".to_owned(),
1497                        col_type: ColumnType::Integer,
1498                        auto_increment: false,
1499                        not_null: true,
1500                        unique: false,
1501                        unique_key: None,
1502                    },
1503                    Column {
1504                        name: "parent_id2".to_owned(),
1505                        col_type: ColumnType::Integer,
1506                        auto_increment: false,
1507                        not_null: true,
1508                        unique: false,
1509                        unique_key: None,
1510                    },
1511                ],
1512                relations: vec![Relation {
1513                    ref_table: "parent".to_owned(),
1514                    columns: vec!["parent_id1".to_owned(), "parent_id2".to_owned()],
1515                    ref_columns: vec!["id1".to_owned(), "id2".to_owned()],
1516                    rel_type: RelationType::BelongsTo,
1517                    on_delete: None,
1518                    on_update: None,
1519                    self_referencing: false,
1520                    num_suffix: 0,
1521                    impl_related: true,
1522                }],
1523                conjunct_relations: vec![],
1524                primary_keys: vec![PrimaryKey {
1525                    name: "id".to_owned(),
1526                }],
1527            },
1528        ]
1529    }
1530
1531    fn parse_from_file<R>(inner: R) -> io::Result<TokenStream>
1532    where
1533        R: Read,
1534    {
1535        let mut reader = BufReader::new(inner);
1536        let mut lines: Vec<String> = Vec::new();
1537
1538        reader.read_until(b';', &mut Vec::new())?;
1539
1540        let mut line = String::new();
1541        while reader.read_line(&mut line)? > 0 {
1542            lines.push(line.to_owned());
1543            line.clear();
1544        }
1545        let content = lines.join("");
1546        Ok(content.parse().unwrap())
1547    }
1548
1549    fn parse_from_frontend_file<R>(inner: R) -> io::Result<TokenStream>
1550    where
1551        R: Read,
1552    {
1553        let mut reader = BufReader::new(inner);
1554        let mut lines: Vec<String> = Vec::new();
1555
1556        reader.read_until(b'\n', &mut Vec::new())?;
1557
1558        let mut line = String::new();
1559        while reader.read_line(&mut line)? > 0 {
1560            lines.push(line.to_owned());
1561            line.clear();
1562        }
1563        let content = lines.join("");
1564        Ok(content.parse().unwrap())
1565    }
1566
1567    #[test]
1568    fn test_gen_expanded_code_blocks() -> io::Result<()> {
1569        let entities = setup();
1570        const ENTITY_FILES: [&str; 13] = [
1571            include_str!("../../tests/expanded/cake.rs"),
1572            include_str!("../../tests/expanded/cake_filling.rs"),
1573            include_str!("../../tests/expanded/cake_filling_price.rs"),
1574            include_str!("../../tests/expanded/filling.rs"),
1575            include_str!("../../tests/expanded/fruit.rs"),
1576            include_str!("../../tests/expanded/vendor.rs"),
1577            include_str!("../../tests/expanded/rust_keyword.rs"),
1578            include_str!("../../tests/expanded/cake_with_float.rs"),
1579            include_str!("../../tests/expanded/cake_with_double.rs"),
1580            include_str!("../../tests/expanded/collection.rs"),
1581            include_str!("../../tests/expanded/collection_float.rs"),
1582            include_str!("../../tests/expanded/parent.rs"),
1583            include_str!("../../tests/expanded/child.rs"),
1584        ];
1585        const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [
1586            include_str!("../../tests/expanded_with_schema_name/cake.rs"),
1587            include_str!("../../tests/expanded_with_schema_name/cake_filling.rs"),
1588            include_str!("../../tests/expanded_with_schema_name/cake_filling_price.rs"),
1589            include_str!("../../tests/expanded_with_schema_name/filling.rs"),
1590            include_str!("../../tests/expanded_with_schema_name/fruit.rs"),
1591            include_str!("../../tests/expanded_with_schema_name/vendor.rs"),
1592            include_str!("../../tests/expanded_with_schema_name/rust_keyword.rs"),
1593            include_str!("../../tests/expanded_with_schema_name/cake_with_float.rs"),
1594            include_str!("../../tests/expanded_with_schema_name/cake_with_double.rs"),
1595            include_str!("../../tests/expanded_with_schema_name/collection.rs"),
1596            include_str!("../../tests/expanded_with_schema_name/collection_float.rs"),
1597            include_str!("../../tests/expanded_with_schema_name/parent.rs"),
1598            include_str!("../../tests/expanded_with_schema_name/child.rs"),
1599        ];
1600
1601        assert_eq!(entities.len(), ENTITY_FILES.len());
1602
1603        for (i, entity) in entities.iter().enumerate() {
1604            assert_eq!(
1605                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
1606                EntityWriter::gen_expanded_code_blocks(
1607                    entity,
1608                    &crate::WithSerde::None,
1609                    &default_column_option(),
1610                    &None,
1611                    false,
1612                    false,
1613                    &TokenStream::new(),
1614                    &TokenStream::new(),
1615                    &TokenStream::new(),
1616                    false,
1617                    true,
1618                )
1619                .into_iter()
1620                .skip(1)
1621                .fold(TokenStream::new(), |mut acc, tok| {
1622                    acc.extend(tok);
1623                    acc
1624                })
1625                .to_string()
1626            );
1627            assert_eq!(
1628                parse_from_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(),
1629                EntityWriter::gen_expanded_code_blocks(
1630                    entity,
1631                    &crate::WithSerde::None,
1632                    &default_column_option(),
1633                    &Some("schema_name".to_owned()),
1634                    false,
1635                    false,
1636                    &TokenStream::new(),
1637                    &TokenStream::new(),
1638                    &TokenStream::new(),
1639                    false,
1640                    true,
1641                )
1642                .into_iter()
1643                .skip(1)
1644                .fold(TokenStream::new(), |mut acc, tok| {
1645                    acc.extend(tok);
1646                    acc
1647                })
1648                .to_string()
1649            );
1650        }
1651
1652        Ok(())
1653    }
1654
1655    #[test]
1656    fn test_gen_compact_code_blocks() -> io::Result<()> {
1657        let entities = setup();
1658        const ENTITY_FILES: [&str; 13] = [
1659            include_str!("../../tests/compact/cake.rs"),
1660            include_str!("../../tests/compact/cake_filling.rs"),
1661            include_str!("../../tests/compact/cake_filling_price.rs"),
1662            include_str!("../../tests/compact/filling.rs"),
1663            include_str!("../../tests/compact/fruit.rs"),
1664            include_str!("../../tests/compact/vendor.rs"),
1665            include_str!("../../tests/compact/rust_keyword.rs"),
1666            include_str!("../../tests/compact/cake_with_float.rs"),
1667            include_str!("../../tests/compact/cake_with_double.rs"),
1668            include_str!("../../tests/compact/collection.rs"),
1669            include_str!("../../tests/compact/collection_float.rs"),
1670            include_str!("../../tests/compact/parent.rs"),
1671            include_str!("../../tests/compact/child.rs"),
1672        ];
1673        const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [
1674            include_str!("../../tests/compact_with_schema_name/cake.rs"),
1675            include_str!("../../tests/compact_with_schema_name/cake_filling.rs"),
1676            include_str!("../../tests/compact_with_schema_name/cake_filling_price.rs"),
1677            include_str!("../../tests/compact_with_schema_name/filling.rs"),
1678            include_str!("../../tests/compact_with_schema_name/fruit.rs"),
1679            include_str!("../../tests/compact_with_schema_name/vendor.rs"),
1680            include_str!("../../tests/compact_with_schema_name/rust_keyword.rs"),
1681            include_str!("../../tests/compact_with_schema_name/cake_with_float.rs"),
1682            include_str!("../../tests/compact_with_schema_name/cake_with_double.rs"),
1683            include_str!("../../tests/compact_with_schema_name/collection.rs"),
1684            include_str!("../../tests/compact_with_schema_name/collection_float.rs"),
1685            include_str!("../../tests/compact_with_schema_name/parent.rs"),
1686            include_str!("../../tests/compact_with_schema_name/child.rs"),
1687        ];
1688
1689        assert_eq!(entities.len(), ENTITY_FILES.len());
1690
1691        for (i, entity) in entities.iter().enumerate() {
1692            assert_eq!(
1693                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
1694                EntityWriter::gen_compact_code_blocks(
1695                    entity,
1696                    &crate::WithSerde::None,
1697                    &default_column_option(),
1698                    &None,
1699                    false,
1700                    false,
1701                    &TokenStream::new(),
1702                    &TokenStream::new(),
1703                    &TokenStream::new(),
1704                    false,
1705                    true,
1706                )
1707                .into_iter()
1708                .skip(1)
1709                .fold(TokenStream::new(), |mut acc, tok| {
1710                    acc.extend(tok);
1711                    acc
1712                })
1713                .to_string()
1714            );
1715            assert_eq!(
1716                parse_from_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(),
1717                EntityWriter::gen_compact_code_blocks(
1718                    entity,
1719                    &crate::WithSerde::None,
1720                    &default_column_option(),
1721                    &Some("schema_name".to_owned()),
1722                    false,
1723                    false,
1724                    &TokenStream::new(),
1725                    &TokenStream::new(),
1726                    &TokenStream::new(),
1727                    false,
1728                    true,
1729                )
1730                .into_iter()
1731                .skip(1)
1732                .fold(TokenStream::new(), |mut acc, tok| {
1733                    acc.extend(tok);
1734                    acc
1735                })
1736                .to_string()
1737            );
1738        }
1739
1740        Ok(())
1741    }
1742
1743    #[test]
1744    fn test_gen_frontend_code_blocks() -> io::Result<()> {
1745        let entities = setup();
1746        const ENTITY_FILES: [&str; 13] = [
1747            include_str!("../../tests/frontend/cake.rs"),
1748            include_str!("../../tests/frontend/cake_filling.rs"),
1749            include_str!("../../tests/frontend/cake_filling_price.rs"),
1750            include_str!("../../tests/frontend/filling.rs"),
1751            include_str!("../../tests/frontend/fruit.rs"),
1752            include_str!("../../tests/frontend/vendor.rs"),
1753            include_str!("../../tests/frontend/rust_keyword.rs"),
1754            include_str!("../../tests/frontend/cake_with_float.rs"),
1755            include_str!("../../tests/frontend/cake_with_double.rs"),
1756            include_str!("../../tests/frontend/collection.rs"),
1757            include_str!("../../tests/frontend/collection_float.rs"),
1758            include_str!("../../tests/frontend/parent.rs"),
1759            include_str!("../../tests/frontend/child.rs"),
1760        ];
1761        const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [
1762            include_str!("../../tests/frontend_with_schema_name/cake.rs"),
1763            include_str!("../../tests/frontend_with_schema_name/cake_filling.rs"),
1764            include_str!("../../tests/frontend_with_schema_name/cake_filling_price.rs"),
1765            include_str!("../../tests/frontend_with_schema_name/filling.rs"),
1766            include_str!("../../tests/frontend_with_schema_name/fruit.rs"),
1767            include_str!("../../tests/frontend_with_schema_name/vendor.rs"),
1768            include_str!("../../tests/frontend_with_schema_name/rust_keyword.rs"),
1769            include_str!("../../tests/frontend_with_schema_name/cake_with_float.rs"),
1770            include_str!("../../tests/frontend_with_schema_name/cake_with_double.rs"),
1771            include_str!("../../tests/frontend_with_schema_name/collection.rs"),
1772            include_str!("../../tests/frontend_with_schema_name/collection_float.rs"),
1773            include_str!("../../tests/frontend_with_schema_name/parent.rs"),
1774            include_str!("../../tests/frontend_with_schema_name/child.rs"),
1775        ];
1776
1777        assert_eq!(entities.len(), ENTITY_FILES.len());
1778
1779        for (i, entity) in entities.iter().enumerate() {
1780            assert_eq!(
1781                dbg!(parse_from_frontend_file(ENTITY_FILES[i].as_bytes())?.to_string()),
1782                EntityWriter::gen_frontend_code_blocks(
1783                    entity,
1784                    &crate::WithSerde::None,
1785                    &default_column_option(),
1786                    &None,
1787                    false,
1788                    false,
1789                    &TokenStream::new(),
1790                    &TokenStream::new(),
1791                    &TokenStream::new(),
1792                    false,
1793                    true,
1794                )
1795                .into_iter()
1796                .skip(1)
1797                .fold(TokenStream::new(), |mut acc, tok| {
1798                    acc.extend(tok);
1799                    acc
1800                })
1801                .to_string()
1802            );
1803            assert_eq!(
1804                parse_from_frontend_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(),
1805                EntityWriter::gen_frontend_code_blocks(
1806                    entity,
1807                    &crate::WithSerde::None,
1808                    &default_column_option(),
1809                    &Some("schema_name".to_owned()),
1810                    false,
1811                    false,
1812                    &TokenStream::new(),
1813                    &TokenStream::new(),
1814                    &TokenStream::new(),
1815                    false,
1816                    true,
1817                )
1818                .into_iter()
1819                .skip(1)
1820                .fold(TokenStream::new(), |mut acc, tok| {
1821                    acc.extend(tok);
1822                    acc
1823                })
1824                .to_string()
1825            );
1826        }
1827
1828        Ok(())
1829    }
1830
1831    #[test]
1832    fn test_gen_with_serde() -> io::Result<()> {
1833        let cake_entity = setup().get(0).unwrap().clone();
1834
1835        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
1836
1837        // Compact code blocks
1838        assert_eq!(
1839            comparable_file_string(include_str!("../../tests/compact_with_serde/cake_none.rs"))?,
1840            generated_to_string(EntityWriter::gen_compact_code_blocks(
1841                &cake_entity,
1842                &WithSerde::None,
1843                &default_column_option(),
1844                &None,
1845                false,
1846                false,
1847                &TokenStream::new(),
1848                &TokenStream::new(),
1849                &TokenStream::new(),
1850                false,
1851                true,
1852            ))
1853        );
1854        assert_eq!(
1855            comparable_file_string(include_str!(
1856                "../../tests/compact_with_serde/cake_serialize.rs"
1857            ))?,
1858            generated_to_string(EntityWriter::gen_compact_code_blocks(
1859                &cake_entity,
1860                &WithSerde::Serialize,
1861                &default_column_option(),
1862                &None,
1863                false,
1864                false,
1865                &TokenStream::new(),
1866                &TokenStream::new(),
1867                &TokenStream::new(),
1868                false,
1869                true,
1870            ))
1871        );
1872        assert_eq!(
1873            comparable_file_string(include_str!(
1874                "../../tests/compact_with_serde/cake_deserialize.rs"
1875            ))?,
1876            generated_to_string(EntityWriter::gen_compact_code_blocks(
1877                &cake_entity,
1878                &WithSerde::Deserialize,
1879                &default_column_option(),
1880                &None,
1881                true,
1882                false,
1883                &TokenStream::new(),
1884                &TokenStream::new(),
1885                &TokenStream::new(),
1886                false,
1887                true,
1888            ))
1889        );
1890        assert_eq!(
1891            comparable_file_string(include_str!("../../tests/compact_with_serde/cake_both.rs"))?,
1892            generated_to_string(EntityWriter::gen_compact_code_blocks(
1893                &cake_entity,
1894                &WithSerde::Both,
1895                &default_column_option(),
1896                &None,
1897                true,
1898                false,
1899                &TokenStream::new(),
1900                &TokenStream::new(),
1901                &TokenStream::new(),
1902                false,
1903                true,
1904            ))
1905        );
1906
1907        // Expanded code blocks
1908        assert_eq!(
1909            comparable_file_string(include_str!("../../tests/expanded_with_serde/cake_none.rs"))?,
1910            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1911                &cake_entity,
1912                &WithSerde::None,
1913                &default_column_option(),
1914                &None,
1915                false,
1916                false,
1917                &TokenStream::new(),
1918                &TokenStream::new(),
1919                &TokenStream::new(),
1920                false,
1921                true,
1922            ))
1923        );
1924        assert_eq!(
1925            comparable_file_string(include_str!(
1926                "../../tests/expanded_with_serde/cake_serialize.rs"
1927            ))?,
1928            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1929                &cake_entity,
1930                &WithSerde::Serialize,
1931                &default_column_option(),
1932                &None,
1933                false,
1934                false,
1935                &TokenStream::new(),
1936                &TokenStream::new(),
1937                &TokenStream::new(),
1938                false,
1939                true,
1940            ))
1941        );
1942        assert_eq!(
1943            comparable_file_string(include_str!(
1944                "../../tests/expanded_with_serde/cake_deserialize.rs"
1945            ))?,
1946            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1947                &cake_entity,
1948                &WithSerde::Deserialize,
1949                &default_column_option(),
1950                &None,
1951                true,
1952                false,
1953                &TokenStream::new(),
1954                &TokenStream::new(),
1955                &TokenStream::new(),
1956                false,
1957                true,
1958            ))
1959        );
1960        assert_eq!(
1961            comparable_file_string(include_str!("../../tests/expanded_with_serde/cake_both.rs"))?,
1962            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1963                &cake_entity,
1964                &WithSerde::Both,
1965                &default_column_option(),
1966                &None,
1967                true,
1968                false,
1969                &TokenStream::new(),
1970                &TokenStream::new(),
1971                &TokenStream::new(),
1972                false,
1973                true,
1974            ))
1975        );
1976
1977        // Frontend code blocks
1978        assert_eq!(
1979            comparable_file_string(include_str!("../../tests/frontend_with_serde/cake_none.rs"))?,
1980            generated_to_string(EntityWriter::gen_frontend_code_blocks(
1981                &cake_entity,
1982                &WithSerde::None,
1983                &default_column_option(),
1984                &None,
1985                false,
1986                false,
1987                &TokenStream::new(),
1988                &TokenStream::new(),
1989                &TokenStream::new(),
1990                false,
1991                true,
1992            ))
1993        );
1994        assert_eq!(
1995            comparable_file_string(include_str!(
1996                "../../tests/frontend_with_serde/cake_serialize.rs"
1997            ))?,
1998            generated_to_string(EntityWriter::gen_frontend_code_blocks(
1999                &cake_entity,
2000                &WithSerde::Serialize,
2001                &default_column_option(),
2002                &None,
2003                false,
2004                false,
2005                &TokenStream::new(),
2006                &TokenStream::new(),
2007                &TokenStream::new(),
2008                false,
2009                true,
2010            ))
2011        );
2012        assert_eq!(
2013            comparable_file_string(include_str!(
2014                "../../tests/frontend_with_serde/cake_deserialize.rs"
2015            ))?,
2016            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2017                &cake_entity,
2018                &WithSerde::Deserialize,
2019                &default_column_option(),
2020                &None,
2021                true,
2022                false,
2023                &TokenStream::new(),
2024                &TokenStream::new(),
2025                &TokenStream::new(),
2026                false,
2027                true,
2028            ))
2029        );
2030        assert_eq!(
2031            comparable_file_string(include_str!("../../tests/frontend_with_serde/cake_both.rs"))?,
2032            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2033                &cake_entity,
2034                &WithSerde::Both,
2035                &default_column_option(),
2036                &None,
2037                true,
2038                false,
2039                &TokenStream::new(),
2040                &TokenStream::new(),
2041                &TokenStream::new(),
2042                false,
2043                true,
2044            ))
2045        );
2046
2047        Ok(())
2048    }
2049
2050    #[test]
2051    fn test_gen_with_seaography() -> io::Result<()> {
2052        let cake_entity = Entity {
2053            table_name: "cake".to_owned(),
2054            columns: vec![
2055                Column {
2056                    name: "id".to_owned(),
2057                    col_type: ColumnType::Integer,
2058                    auto_increment: true,
2059                    not_null: true,
2060                    unique: false,
2061                    unique_key: None,
2062                },
2063                Column {
2064                    name: "name".to_owned(),
2065                    col_type: ColumnType::Text,
2066                    auto_increment: false,
2067                    not_null: false,
2068                    unique: false,
2069                    unique_key: None,
2070                },
2071                Column {
2072                    name: "base_id".to_owned(),
2073                    col_type: ColumnType::Integer,
2074                    auto_increment: false,
2075                    not_null: false,
2076                    unique: false,
2077                    unique_key: None,
2078                },
2079            ],
2080            relations: vec![
2081                Relation {
2082                    ref_table: "fruit".to_owned(),
2083                    columns: vec![],
2084                    ref_columns: vec![],
2085                    rel_type: RelationType::HasMany,
2086                    on_delete: None,
2087                    on_update: None,
2088                    self_referencing: false,
2089                    num_suffix: 0,
2090                    impl_related: true,
2091                },
2092                Relation {
2093                    ref_table: "cake".to_owned(),
2094                    columns: vec![],
2095                    ref_columns: vec![],
2096                    rel_type: RelationType::HasOne,
2097                    on_delete: None,
2098                    on_update: None,
2099                    self_referencing: true,
2100                    num_suffix: 0,
2101                    impl_related: true,
2102                },
2103            ],
2104            conjunct_relations: vec![ConjunctRelation {
2105                via: "cake_filling".to_owned(),
2106                to: "filling".to_owned(),
2107            }],
2108            primary_keys: vec![PrimaryKey {
2109                name: "id".to_owned(),
2110            }],
2111        };
2112
2113        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2114
2115        // Compact code blocks
2116        assert_eq!(
2117            comparable_file_string(include_str!("../../tests/with_seaography/cake.rs"))?,
2118            generated_to_string(EntityWriter::gen_compact_code_blocks(
2119                &cake_entity,
2120                &WithSerde::None,
2121                &default_column_option(),
2122                &None,
2123                false,
2124                false,
2125                &TokenStream::new(),
2126                &TokenStream::new(),
2127                &TokenStream::new(),
2128                true,
2129                true,
2130            ))
2131        );
2132
2133        // Expanded code blocks
2134        assert_eq!(
2135            comparable_file_string(include_str!("../../tests/with_seaography/cake_expanded.rs"))?,
2136            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2137                &cake_entity,
2138                &WithSerde::None,
2139                &default_column_option(),
2140                &None,
2141                false,
2142                false,
2143                &TokenStream::new(),
2144                &TokenStream::new(),
2145                &TokenStream::new(),
2146                true,
2147                true,
2148            ))
2149        );
2150
2151        // Frontend code blocks
2152        assert_eq!(
2153            comparable_file_string(include_str!("../../tests/with_seaography/cake_frontend.rs"))?,
2154            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2155                &cake_entity,
2156                &WithSerde::None,
2157                &default_column_option(),
2158                &None,
2159                false,
2160                false,
2161                &TokenStream::new(),
2162                &TokenStream::new(),
2163                &TokenStream::new(),
2164                true,
2165                true,
2166            ))
2167        );
2168
2169        Ok(())
2170    }
2171
2172    #[test]
2173    fn test_gen_with_seaography_mod() -> io::Result<()> {
2174        use crate::ActiveEnum;
2175        use sea_query::IntoIden;
2176
2177        let entities = setup();
2178        let enums = vec![
2179            (
2180                "coinflip_result_type",
2181                ActiveEnum {
2182                    enum_name: Alias::new("coinflip_result_type").into_iden(),
2183                    values: vec!["HEADS", "TAILS"]
2184                        .into_iter()
2185                        .map(|variant| Alias::new(variant).into_iden())
2186                        .collect(),
2187                },
2188            ),
2189            (
2190                "media_type",
2191                ActiveEnum {
2192                    enum_name: Alias::new("media_type").into_iden(),
2193                    values: vec![
2194                        "UNKNOWN",
2195                        "BITMAP",
2196                        "DRAWING",
2197                        "AUDIO",
2198                        "VIDEO",
2199                        "MULTIMEDIA",
2200                        "OFFICE",
2201                        "TEXT",
2202                        "EXECUTABLE",
2203                        "ARCHIVE",
2204                        "3D",
2205                    ]
2206                    .into_iter()
2207                    .map(|variant| Alias::new(variant).into_iden())
2208                    .collect(),
2209                },
2210            ),
2211        ]
2212        .into_iter()
2213        .map(|(k, v)| (k.to_string(), v))
2214        .collect();
2215
2216        assert_eq!(
2217            comparable_file_string(include_str!("../../tests/with_seaography/mod.rs"))?,
2218            generated_to_string(vec![EntityWriter::gen_seaography_entity_mod(
2219                &entities, &enums,
2220            )])
2221        );
2222
2223        Ok(())
2224    }
2225
2226    #[test]
2227    fn test_gen_with_derives() -> io::Result<()> {
2228        let mut cake_entity = setup().get_mut(0).unwrap().clone();
2229
2230        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2231
2232        // Compact code blocks
2233        assert_eq!(
2234            comparable_file_string(include_str!(
2235                "../../tests/compact_with_derives/cake_none.rs"
2236            ))?,
2237            generated_to_string(EntityWriter::gen_compact_code_blocks(
2238                &cake_entity,
2239                &WithSerde::None,
2240                &default_column_option(),
2241                &None,
2242                false,
2243                false,
2244                &TokenStream::new(),
2245                &TokenStream::new(),
2246                &TokenStream::new(),
2247                false,
2248                true,
2249            ))
2250        );
2251        assert_eq!(
2252            comparable_file_string(include_str!("../../tests/compact_with_derives/cake_one.rs"))?,
2253            generated_to_string(EntityWriter::gen_compact_code_blocks(
2254                &cake_entity,
2255                &WithSerde::None,
2256                &default_column_option(),
2257                &None,
2258                false,
2259                false,
2260                &bonus_derive(["ts_rs::TS"]),
2261                &TokenStream::new(),
2262                &TokenStream::new(),
2263                false,
2264                true,
2265            ))
2266        );
2267        assert_eq!(
2268            comparable_file_string(include_str!(
2269                "../../tests/compact_with_derives/cake_multiple.rs"
2270            ))?,
2271            generated_to_string(EntityWriter::gen_compact_code_blocks(
2272                &cake_entity,
2273                &WithSerde::None,
2274                &default_column_option(),
2275                &None,
2276                false,
2277                false,
2278                &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]),
2279                &TokenStream::new(),
2280                &TokenStream::new(),
2281                false,
2282                true,
2283            ))
2284        );
2285
2286        // Expanded code blocks
2287        assert_eq!(
2288            comparable_file_string(include_str!(
2289                "../../tests/expanded_with_derives/cake_none.rs"
2290            ))?,
2291            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2292                &cake_entity,
2293                &WithSerde::None,
2294                &default_column_option(),
2295                &None,
2296                false,
2297                false,
2298                &TokenStream::new(),
2299                &TokenStream::new(),
2300                &TokenStream::new(),
2301                false,
2302                true,
2303            ))
2304        );
2305        assert_eq!(
2306            comparable_file_string(include_str!(
2307                "../../tests/expanded_with_derives/cake_one.rs"
2308            ))?,
2309            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2310                &cake_entity,
2311                &WithSerde::None,
2312                &default_column_option(),
2313                &None,
2314                false,
2315                false,
2316                &bonus_derive(["ts_rs::TS"]),
2317                &TokenStream::new(),
2318                &TokenStream::new(),
2319                false,
2320                true,
2321            ))
2322        );
2323        assert_eq!(
2324            comparable_file_string(include_str!(
2325                "../../tests/expanded_with_derives/cake_multiple.rs"
2326            ))?,
2327            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2328                &cake_entity,
2329                &WithSerde::None,
2330                &default_column_option(),
2331                &None,
2332                false,
2333                false,
2334                &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]),
2335                &TokenStream::new(),
2336                &TokenStream::new(),
2337                false,
2338                true,
2339            ))
2340        );
2341
2342        // Frontend code blocks
2343        assert_eq!(
2344            comparable_file_string(include_str!(
2345                "../../tests/frontend_with_derives/cake_none.rs"
2346            ))?,
2347            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2348                &cake_entity,
2349                &WithSerde::None,
2350                &default_column_option(),
2351                &None,
2352                false,
2353                false,
2354                &TokenStream::new(),
2355                &TokenStream::new(),
2356                &TokenStream::new(),
2357                false,
2358                true,
2359            ))
2360        );
2361        assert_eq!(
2362            comparable_file_string(include_str!(
2363                "../../tests/frontend_with_derives/cake_one.rs"
2364            ))?,
2365            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2366                &cake_entity,
2367                &WithSerde::None,
2368                &default_column_option(),
2369                &None,
2370                false,
2371                false,
2372                &bonus_derive(["ts_rs::TS"]),
2373                &TokenStream::new(),
2374                &TokenStream::new(),
2375                false,
2376                true,
2377            ))
2378        );
2379        assert_eq!(
2380            comparable_file_string(include_str!(
2381                "../../tests/frontend_with_derives/cake_multiple.rs"
2382            ))?,
2383            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2384                &cake_entity,
2385                &WithSerde::None,
2386                &default_column_option(),
2387                &None,
2388                false,
2389                false,
2390                &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]),
2391                &TokenStream::new(),
2392                &TokenStream::new(),
2393                false,
2394                true,
2395            ))
2396        );
2397
2398        // Make the `name` column of `cake` entity as hidden column
2399        cake_entity.columns[1].name = "_name".into();
2400
2401        assert_serde_variant_results(
2402            &cake_entity,
2403            &(
2404                include_str!("../../tests/compact_with_serde/cake_serialize_with_hidden_column.rs"),
2405                WithSerde::Serialize,
2406                None,
2407            ),
2408            Box::new(EntityWriter::gen_compact_code_blocks),
2409        )?;
2410        assert_serde_variant_results(
2411            &cake_entity,
2412            &(
2413                include_str!(
2414                    "../../tests/expanded_with_serde/cake_serialize_with_hidden_column.rs"
2415                ),
2416                WithSerde::Serialize,
2417                None,
2418            ),
2419            Box::new(EntityWriter::gen_expanded_code_blocks),
2420        )?;
2421        assert_serde_variant_results(
2422            &cake_entity,
2423            &(
2424                include_str!(
2425                    "../../tests/frontend_with_serde/cake_serialize_with_hidden_column.rs"
2426                ),
2427                WithSerde::Serialize,
2428                None,
2429            ),
2430            Box::new(EntityWriter::gen_frontend_code_blocks),
2431        )?;
2432
2433        Ok(())
2434    }
2435
2436    #[test]
2437    fn test_gen_with_column_derives() -> io::Result<()> {
2438        let cake_entity = setup().get_mut(0).unwrap().clone();
2439
2440        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2441
2442        assert_eq!(
2443            comparable_file_string(include_str!(
2444                "../../tests/expanded_with_column_derives/cake_one.rs"
2445            ))?,
2446            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2447                &cake_entity,
2448                &WithSerde::None,
2449                &default_column_option(),
2450                &None,
2451                false,
2452                false,
2453                &TokenStream::new(),
2454                &TokenStream::new(),
2455                &bonus_derive(["async_graphql::Enum"]),
2456                false,
2457                true,
2458            ))
2459        );
2460        assert_eq!(
2461            comparable_file_string(include_str!(
2462                "../../tests/expanded_with_column_derives/cake_multiple.rs"
2463            ))?,
2464            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2465                &cake_entity,
2466                &WithSerde::None,
2467                &default_column_option(),
2468                &None,
2469                false,
2470                false,
2471                &TokenStream::new(),
2472                &TokenStream::new(),
2473                &bonus_derive(["async_graphql::Enum", "Eq", "PartialEq"]),
2474                false,
2475                true,
2476            ))
2477        );
2478
2479        Ok(())
2480    }
2481
2482    #[allow(clippy::type_complexity)]
2483    fn assert_serde_variant_results(
2484        cake_entity: &Entity,
2485        entity_serde_variant: &(&str, WithSerde, Option<String>),
2486        generator: Box<
2487            dyn Fn(
2488                &Entity,
2489                &WithSerde,
2490                &ColumnOption,
2491                &Option<String>,
2492                bool,
2493                bool,
2494                &TokenStream,
2495                &TokenStream,
2496                &TokenStream,
2497                bool,
2498                bool,
2499            ) -> Vec<TokenStream>,
2500        >,
2501    ) -> io::Result<()> {
2502        let mut reader = BufReader::new(entity_serde_variant.0.as_bytes());
2503        let mut lines: Vec<String> = Vec::new();
2504        let serde_skip_deserializing_primary_key = matches!(
2505            entity_serde_variant.1,
2506            WithSerde::Both | WithSerde::Deserialize
2507        );
2508        let serde_skip_hidden_column = matches!(entity_serde_variant.1, WithSerde::Serialize);
2509
2510        reader.read_until(b'\n', &mut Vec::new())?;
2511
2512        let mut line = String::new();
2513        while reader.read_line(&mut line)? > 0 {
2514            lines.push(line.to_owned());
2515            line.clear();
2516        }
2517        let content = lines.join("");
2518        let expected: TokenStream = content.parse().unwrap();
2519        println!("{:?}", entity_serde_variant.1);
2520        let generated = generator(
2521            cake_entity,
2522            &entity_serde_variant.1,
2523            &default_column_option(),
2524            &entity_serde_variant.2,
2525            serde_skip_deserializing_primary_key,
2526            serde_skip_hidden_column,
2527            &TokenStream::new(),
2528            &TokenStream::new(),
2529            &TokenStream::new(),
2530            false,
2531            true,
2532        )
2533        .into_iter()
2534        .fold(TokenStream::new(), |mut acc, tok| {
2535            acc.extend(tok);
2536            acc
2537        });
2538
2539        assert_eq!(expected.to_string(), generated.to_string());
2540        Ok(())
2541    }
2542
2543    #[test]
2544    fn test_gen_with_attributes() -> io::Result<()> {
2545        let cake_entity = setup().get(0).unwrap().clone();
2546
2547        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2548
2549        // Compact code blocks
2550        assert_eq!(
2551            comparable_file_string(include_str!(
2552                "../../tests/compact_with_attributes/cake_none.rs"
2553            ))?,
2554            generated_to_string(EntityWriter::gen_compact_code_blocks(
2555                &cake_entity,
2556                &WithSerde::None,
2557                &default_column_option(),
2558                &None,
2559                false,
2560                false,
2561                &TokenStream::new(),
2562                &TokenStream::new(),
2563                &TokenStream::new(),
2564                false,
2565                true,
2566            ))
2567        );
2568        assert_eq!(
2569            comparable_file_string(include_str!(
2570                "../../tests/compact_with_attributes/cake_one.rs"
2571            ))?,
2572            generated_to_string(EntityWriter::gen_compact_code_blocks(
2573                &cake_entity,
2574                &WithSerde::None,
2575                &default_column_option(),
2576                &None,
2577                false,
2578                false,
2579                &TokenStream::new(),
2580                &bonus_attributes([r#"serde(rename_all = "camelCase")"#]),
2581                &TokenStream::new(),
2582                false,
2583                true,
2584            ))
2585        );
2586        assert_eq!(
2587            comparable_file_string(include_str!(
2588                "../../tests/compact_with_attributes/cake_multiple.rs"
2589            ))?,
2590            generated_to_string(EntityWriter::gen_compact_code_blocks(
2591                &cake_entity,
2592                &WithSerde::None,
2593                &default_column_option(),
2594                &None,
2595                false,
2596                false,
2597                &TokenStream::new(),
2598                &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]),
2599                &TokenStream::new(),
2600                false,
2601                true,
2602            ))
2603        );
2604
2605        // Expanded code blocks
2606        assert_eq!(
2607            comparable_file_string(include_str!(
2608                "../../tests/expanded_with_attributes/cake_none.rs"
2609            ))?,
2610            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2611                &cake_entity,
2612                &WithSerde::None,
2613                &default_column_option(),
2614                &None,
2615                false,
2616                false,
2617                &TokenStream::new(),
2618                &TokenStream::new(),
2619                &TokenStream::new(),
2620                false,
2621                true,
2622            ))
2623        );
2624        assert_eq!(
2625            comparable_file_string(include_str!(
2626                "../../tests/expanded_with_attributes/cake_one.rs"
2627            ))?,
2628            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2629                &cake_entity,
2630                &WithSerde::None,
2631                &default_column_option(),
2632                &None,
2633                false,
2634                false,
2635                &TokenStream::new(),
2636                &bonus_attributes([r#"serde(rename_all = "camelCase")"#]),
2637                &TokenStream::new(),
2638                false,
2639                true,
2640            ))
2641        );
2642        assert_eq!(
2643            comparable_file_string(include_str!(
2644                "../../tests/expanded_with_attributes/cake_multiple.rs"
2645            ))?,
2646            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2647                &cake_entity,
2648                &WithSerde::None,
2649                &default_column_option(),
2650                &None,
2651                false,
2652                false,
2653                &TokenStream::new(),
2654                &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]),
2655                &TokenStream::new(),
2656                false,
2657                true,
2658            ))
2659        );
2660
2661        // Frontend code blocks
2662        assert_eq!(
2663            comparable_file_string(include_str!(
2664                "../../tests/frontend_with_attributes/cake_none.rs"
2665            ))?,
2666            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2667                &cake_entity,
2668                &WithSerde::None,
2669                &default_column_option(),
2670                &None,
2671                false,
2672                false,
2673                &TokenStream::new(),
2674                &TokenStream::new(),
2675                &TokenStream::new(),
2676                false,
2677                true,
2678            ))
2679        );
2680        assert_eq!(
2681            comparable_file_string(include_str!(
2682                "../../tests/frontend_with_attributes/cake_one.rs"
2683            ))?,
2684            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2685                &cake_entity,
2686                &WithSerde::None,
2687                &default_column_option(),
2688                &None,
2689                false,
2690                false,
2691                &TokenStream::new(),
2692                &bonus_attributes([r#"serde(rename_all = "camelCase")"#]),
2693                &TokenStream::new(),
2694                false,
2695                true,
2696            ))
2697        );
2698        assert_eq!(
2699            comparable_file_string(include_str!(
2700                "../../tests/frontend_with_attributes/cake_multiple.rs"
2701            ))?,
2702            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2703                &cake_entity,
2704                &WithSerde::None,
2705                &default_column_option(),
2706                &None,
2707                false,
2708                false,
2709                &TokenStream::new(),
2710                &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]),
2711                &TokenStream::new(),
2712                false,
2713                true,
2714            ))
2715        );
2716
2717        Ok(())
2718    }
2719
2720    fn generated_to_string(generated: Vec<TokenStream>) -> String {
2721        generated
2722            .into_iter()
2723            .fold(TokenStream::new(), |mut acc, tok| {
2724                acc.extend(tok);
2725                acc
2726            })
2727            .to_string()
2728    }
2729
2730    fn comparable_file_string(file: &str) -> io::Result<String> {
2731        let mut reader = BufReader::new(file.as_bytes());
2732        let mut lines: Vec<String> = Vec::new();
2733
2734        reader.read_until(b'\n', &mut Vec::new())?;
2735
2736        let mut line = String::new();
2737        while reader.read_line(&mut line)? > 0 {
2738            lines.push(line.to_owned());
2739            line.clear();
2740        }
2741        let content = lines.join("");
2742        let expected: TokenStream = content.parse().unwrap();
2743
2744        Ok(expected.to_string())
2745    }
2746
2747    #[test]
2748    fn test_gen_postgres() -> io::Result<()> {
2749        let entities = vec![
2750            // This tests that the JsonBinary column type is annotated
2751            // correctly in compact entity form. More information can be found
2752            // in this issue:
2753            //
2754            // https://github.com/SeaQL/sea-orm/issues/1344
2755            Entity {
2756                table_name: "task".to_owned(),
2757                columns: vec![
2758                    Column {
2759                        name: "id".to_owned(),
2760                        col_type: ColumnType::Integer,
2761                        auto_increment: true,
2762                        not_null: true,
2763                        unique: false,
2764                        unique_key: None,
2765                    },
2766                    Column {
2767                        name: "payload".to_owned(),
2768                        col_type: ColumnType::Json,
2769                        auto_increment: false,
2770                        not_null: true,
2771                        unique: false,
2772                        unique_key: None,
2773                    },
2774                    Column {
2775                        name: "payload_binary".to_owned(),
2776                        col_type: ColumnType::JsonBinary,
2777                        auto_increment: false,
2778                        not_null: true,
2779                        unique: false,
2780                        unique_key: None,
2781                    },
2782                ],
2783                relations: vec![],
2784                conjunct_relations: vec![],
2785                primary_keys: vec![PrimaryKey {
2786                    name: "id".to_owned(),
2787                }],
2788            },
2789        ];
2790        const ENTITY_FILES: [&str; 1] = [include_str!("../../tests/postgres/binary_json.rs")];
2791
2792        const ENTITY_FILES_EXPANDED: [&str; 1] =
2793            [include_str!("../../tests/postgres/binary_json_expanded.rs")];
2794
2795        assert_eq!(entities.len(), ENTITY_FILES.len());
2796
2797        for (i, entity) in entities.iter().enumerate() {
2798            assert_eq!(
2799                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
2800                EntityWriter::gen_compact_code_blocks(
2801                    entity,
2802                    &crate::WithSerde::None,
2803                    &default_column_option(),
2804                    &None,
2805                    false,
2806                    false,
2807                    &TokenStream::new(),
2808                    &TokenStream::new(),
2809                    &TokenStream::new(),
2810                    false,
2811                    true,
2812                )
2813                .into_iter()
2814                .skip(1)
2815                .fold(TokenStream::new(), |mut acc, tok| {
2816                    acc.extend(tok);
2817                    acc
2818                })
2819                .to_string()
2820            );
2821            assert_eq!(
2822                parse_from_file(ENTITY_FILES_EXPANDED[i].as_bytes())?.to_string(),
2823                EntityWriter::gen_expanded_code_blocks(
2824                    entity,
2825                    &crate::WithSerde::None,
2826                    &default_column_option(),
2827                    &Some("schema_name".to_owned()),
2828                    false,
2829                    false,
2830                    &TokenStream::new(),
2831                    &TokenStream::new(),
2832                    &TokenStream::new(),
2833                    false,
2834                    true,
2835                )
2836                .into_iter()
2837                .skip(1)
2838                .fold(TokenStream::new(), |mut acc, tok| {
2839                    acc.extend(tok);
2840                    acc
2841                })
2842                .to_string()
2843            );
2844        }
2845
2846        Ok(())
2847    }
2848
2849    #[test]
2850    fn test_gen_import_active_enum() -> io::Result<()> {
2851        let entities = vec![
2852            Entity {
2853                table_name: "tea_pairing".to_owned(),
2854                columns: vec![
2855                    Column {
2856                        name: "id".to_owned(),
2857                        col_type: ColumnType::Integer,
2858                        auto_increment: true,
2859                        not_null: true,
2860                        unique: false,
2861                        unique_key: None,
2862                    },
2863                    Column {
2864                        name: "first_tea".to_owned(),
2865                        col_type: ColumnType::Enum {
2866                            name: SeaRc::new(Alias::new("tea_enum")),
2867                            variants: vec![
2868                                SeaRc::new(Alias::new("everyday_tea")),
2869                                SeaRc::new(Alias::new("breakfast_tea")),
2870                            ],
2871                        },
2872                        auto_increment: false,
2873                        not_null: true,
2874                        unique: false,
2875                        unique_key: None,
2876                    },
2877                    Column {
2878                        name: "second_tea".to_owned(),
2879                        col_type: ColumnType::Enum {
2880                            name: SeaRc::new(Alias::new("tea_enum")),
2881                            variants: vec![
2882                                SeaRc::new(Alias::new("everyday_tea")),
2883                                SeaRc::new(Alias::new("breakfast_tea")),
2884                            ],
2885                        },
2886                        auto_increment: false,
2887                        not_null: true,
2888                        unique: false,
2889                        unique_key: None,
2890                    },
2891                ],
2892                relations: vec![],
2893                conjunct_relations: vec![],
2894                primary_keys: vec![PrimaryKey {
2895                    name: "id".to_owned(),
2896                }],
2897            },
2898            Entity {
2899                table_name: "tea_pairing_with_size".to_owned(),
2900                columns: vec![
2901                    Column {
2902                        name: "id".to_owned(),
2903                        col_type: ColumnType::Integer,
2904                        auto_increment: true,
2905                        not_null: true,
2906                        unique: false,
2907                        unique_key: None,
2908                    },
2909                    Column {
2910                        name: "first_tea".to_owned(),
2911                        col_type: ColumnType::Enum {
2912                            name: SeaRc::new(Alias::new("tea_enum")),
2913                            variants: vec![
2914                                SeaRc::new(Alias::new("everyday_tea")),
2915                                SeaRc::new(Alias::new("breakfast_tea")),
2916                            ],
2917                        },
2918                        auto_increment: false,
2919                        not_null: true,
2920                        unique: false,
2921                        unique_key: None,
2922                    },
2923                    Column {
2924                        name: "second_tea".to_owned(),
2925                        col_type: ColumnType::Enum {
2926                            name: SeaRc::new(Alias::new("tea_enum")),
2927                            variants: vec![
2928                                SeaRc::new(Alias::new("everyday_tea")),
2929                                SeaRc::new(Alias::new("breakfast_tea")),
2930                            ],
2931                        },
2932                        auto_increment: false,
2933                        not_null: true,
2934                        unique: false,
2935                        unique_key: None,
2936                    },
2937                    Column {
2938                        name: "size".to_owned(),
2939                        col_type: ColumnType::Enum {
2940                            name: SeaRc::new(Alias::new("tea_size")),
2941                            variants: vec![
2942                                SeaRc::new(Alias::new("small")),
2943                                SeaRc::new(Alias::new("medium")),
2944                                SeaRc::new(Alias::new("huge")),
2945                            ],
2946                        },
2947                        auto_increment: false,
2948                        not_null: true,
2949                        unique: false,
2950                        unique_key: None,
2951                    },
2952                ],
2953                relations: vec![],
2954                conjunct_relations: vec![],
2955                primary_keys: vec![PrimaryKey {
2956                    name: "id".to_owned(),
2957                }],
2958            },
2959        ];
2960
2961        assert_eq!(
2962            quote!(
2963                use super::sea_orm_active_enums::TeaEnum;
2964            )
2965            .to_string(),
2966            EntityWriter::gen_import_active_enum(&entities[0]).to_string()
2967        );
2968
2969        assert_eq!(
2970            quote!(
2971                use super::sea_orm_active_enums::TeaEnum;
2972                use super::sea_orm_active_enums::TeaSize;
2973            )
2974            .to_string(),
2975            EntityWriter::gen_import_active_enum(&entities[1]).to_string()
2976        );
2977
2978        Ok(())
2979    }
2980
2981    #[test]
2982    fn test_gen_dense_code_blocks() -> io::Result<()> {
2983        let entities = setup();
2984        const ENTITY_FILES: [&str; 13] = [
2985            include_str!("../../tests/dense/cake.rs"),
2986            include_str!("../../tests/dense/cake_filling.rs"),
2987            include_str!("../../tests/dense/cake_filling_price.rs"),
2988            include_str!("../../tests/dense/filling.rs"),
2989            include_str!("../../tests/dense/fruit.rs"),
2990            include_str!("../../tests/dense/vendor.rs"),
2991            include_str!("../../tests/dense/rust_keyword.rs"),
2992            include_str!("../../tests/dense/cake_with_float.rs"),
2993            include_str!("../../tests/dense/cake_with_double.rs"),
2994            include_str!("../../tests/dense/collection.rs"),
2995            include_str!("../../tests/dense/collection_float.rs"),
2996            include_str!("../../tests/dense/parent.rs"),
2997            include_str!("../../tests/dense/child.rs"),
2998        ];
2999
3000        assert_eq!(entities.len(), ENTITY_FILES.len());
3001
3002        for (i, entity) in entities.iter().enumerate() {
3003            assert_eq!(
3004                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
3005                EntityWriter::gen_dense_code_blocks(
3006                    entity,
3007                    &crate::WithSerde::None,
3008                    &default_column_option(),
3009                    &None,
3010                    false,
3011                    false,
3012                    &TokenStream::new(),
3013                    &TokenStream::new(),
3014                    &TokenStream::new(),
3015                    false,
3016                    true,
3017                )
3018                .into_iter()
3019                .skip(1)
3020                .fold(TokenStream::new(), |mut acc, tok| {
3021                    acc.extend(tok);
3022                    acc
3023                })
3024                .to_string()
3025            );
3026        }
3027
3028        Ok(())
3029    }
3030}