sea_orm_codegen/entity/
writer.rs

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