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                        unique_key: None,
827                    },
828                    Column {
829                        name: "name".to_owned(),
830                        col_type: ColumnType::Text,
831                        auto_increment: false,
832                        not_null: false,
833                        unique: false,
834                        unique_key: None,
835                    },
836                ],
837                relations: vec![Relation {
838                    ref_table: "fruit".to_owned(),
839                    columns: vec![],
840                    ref_columns: vec![],
841                    rel_type: RelationType::HasMany,
842                    on_delete: None,
843                    on_update: None,
844                    self_referencing: false,
845                    num_suffix: 0,
846                    impl_related: true,
847                }],
848                conjunct_relations: vec![ConjunctRelation {
849                    via: "cake_filling".to_owned(),
850                    to: "filling".to_owned(),
851                }],
852                primary_keys: vec![PrimaryKey {
853                    name: "id".to_owned(),
854                }],
855            },
856            Entity {
857                table_name: "_cake_filling_".to_owned(),
858                columns: vec![
859                    Column {
860                        name: "cake_id".to_owned(),
861                        col_type: ColumnType::Integer,
862                        auto_increment: false,
863                        not_null: true,
864                        unique: false,
865                        unique_key: None,
866                    },
867                    Column {
868                        name: "filling_id".to_owned(),
869                        col_type: ColumnType::Integer,
870                        auto_increment: false,
871                        not_null: true,
872                        unique: false,
873                        unique_key: None,
874                    },
875                ],
876                relations: vec![
877                    Relation {
878                        ref_table: "cake".to_owned(),
879                        columns: vec!["cake_id".to_owned()],
880                        ref_columns: vec!["id".to_owned()],
881                        rel_type: RelationType::BelongsTo,
882                        on_delete: Some(ForeignKeyAction::Cascade),
883                        on_update: Some(ForeignKeyAction::Cascade),
884                        self_referencing: false,
885                        num_suffix: 0,
886                        impl_related: true,
887                    },
888                    Relation {
889                        ref_table: "filling".to_owned(),
890                        columns: vec!["filling_id".to_owned()],
891                        ref_columns: vec!["id".to_owned()],
892                        rel_type: RelationType::BelongsTo,
893                        on_delete: Some(ForeignKeyAction::Cascade),
894                        on_update: Some(ForeignKeyAction::Cascade),
895                        self_referencing: false,
896                        num_suffix: 0,
897                        impl_related: true,
898                    },
899                ],
900                conjunct_relations: vec![],
901                primary_keys: vec![
902                    PrimaryKey {
903                        name: "cake_id".to_owned(),
904                    },
905                    PrimaryKey {
906                        name: "filling_id".to_owned(),
907                    },
908                ],
909            },
910            Entity {
911                table_name: "cake_filling_price".to_owned(),
912                columns: vec![
913                    Column {
914                        name: "cake_id".to_owned(),
915                        col_type: ColumnType::Integer,
916                        auto_increment: false,
917                        not_null: true,
918                        unique: false,
919                        unique_key: None,
920                    },
921                    Column {
922                        name: "filling_id".to_owned(),
923                        col_type: ColumnType::Integer,
924                        auto_increment: false,
925                        not_null: true,
926                        unique: false,
927                        unique_key: None,
928                    },
929                    Column {
930                        name: "price".to_owned(),
931                        col_type: ColumnType::Decimal(None),
932                        auto_increment: false,
933                        not_null: true,
934                        unique: false,
935                        unique_key: None,
936                    },
937                ],
938                relations: vec![Relation {
939                    ref_table: "cake_filling".to_owned(),
940                    columns: vec!["cake_id".to_owned(), "filling_id".to_owned()],
941                    ref_columns: vec!["cake_id".to_owned(), "filling_id".to_owned()],
942                    rel_type: RelationType::BelongsTo,
943                    on_delete: None,
944                    on_update: None,
945                    self_referencing: false,
946                    num_suffix: 0,
947                    impl_related: true,
948                }],
949                conjunct_relations: vec![],
950                primary_keys: vec![
951                    PrimaryKey {
952                        name: "cake_id".to_owned(),
953                    },
954                    PrimaryKey {
955                        name: "filling_id".to_owned(),
956                    },
957                ],
958            },
959            Entity {
960                table_name: "filling".to_owned(),
961                columns: vec![
962                    Column {
963                        name: "id".to_owned(),
964                        col_type: ColumnType::Integer,
965                        auto_increment: true,
966                        not_null: true,
967                        unique: false,
968                        unique_key: None,
969                    },
970                    Column {
971                        name: "name".to_owned(),
972                        col_type: ColumnType::String(StringLen::N(255)),
973                        auto_increment: false,
974                        not_null: true,
975                        unique: false,
976                        unique_key: None,
977                    },
978                ],
979                relations: vec![],
980                conjunct_relations: vec![ConjunctRelation {
981                    via: "cake_filling".to_owned(),
982                    to: "cake".to_owned(),
983                }],
984                primary_keys: vec![PrimaryKey {
985                    name: "id".to_owned(),
986                }],
987            },
988            Entity {
989                table_name: "fruit".to_owned(),
990                columns: vec![
991                    Column {
992                        name: "id".to_owned(),
993                        col_type: ColumnType::Integer,
994                        auto_increment: true,
995                        not_null: true,
996                        unique: false,
997                        unique_key: None,
998                    },
999                    Column {
1000                        name: "name".to_owned(),
1001                        col_type: ColumnType::String(StringLen::N(255)),
1002                        auto_increment: false,
1003                        not_null: true,
1004                        unique: false,
1005                        unique_key: None,
1006                    },
1007                    Column {
1008                        name: "cake_id".to_owned(),
1009                        col_type: ColumnType::Integer,
1010                        auto_increment: false,
1011                        not_null: false,
1012                        unique: false,
1013                        unique_key: None,
1014                    },
1015                ],
1016                relations: vec![
1017                    Relation {
1018                        ref_table: "cake".to_owned(),
1019                        columns: vec!["cake_id".to_owned()],
1020                        ref_columns: vec!["id".to_owned()],
1021                        rel_type: RelationType::BelongsTo,
1022                        on_delete: None,
1023                        on_update: None,
1024                        self_referencing: false,
1025                        num_suffix: 0,
1026                        impl_related: true,
1027                    },
1028                    Relation {
1029                        ref_table: "vendor".to_owned(),
1030                        columns: vec![],
1031                        ref_columns: vec![],
1032                        rel_type: RelationType::HasMany,
1033                        on_delete: None,
1034                        on_update: None,
1035                        self_referencing: false,
1036                        num_suffix: 0,
1037                        impl_related: true,
1038                    },
1039                ],
1040                conjunct_relations: vec![],
1041                primary_keys: vec![PrimaryKey {
1042                    name: "id".to_owned(),
1043                }],
1044            },
1045            Entity {
1046                table_name: "vendor".to_owned(),
1047                columns: vec![
1048                    Column {
1049                        name: "id".to_owned(),
1050                        col_type: ColumnType::Integer,
1051                        auto_increment: true,
1052                        not_null: true,
1053                        unique: false,
1054                        unique_key: None,
1055                    },
1056                    Column {
1057                        name: "_name_".to_owned(),
1058                        col_type: ColumnType::String(StringLen::N(255)),
1059                        auto_increment: false,
1060                        not_null: true,
1061                        unique: false,
1062                        unique_key: None,
1063                    },
1064                    Column {
1065                        name: "fruitId".to_owned(),
1066                        col_type: ColumnType::Integer,
1067                        auto_increment: false,
1068                        not_null: false,
1069                        unique: false,
1070                        unique_key: None,
1071                    },
1072                ],
1073                relations: vec![Relation {
1074                    ref_table: "fruit".to_owned(),
1075                    columns: vec!["fruitId".to_owned()],
1076                    ref_columns: vec!["id".to_owned()],
1077                    rel_type: RelationType::BelongsTo,
1078                    on_delete: None,
1079                    on_update: None,
1080                    self_referencing: false,
1081                    num_suffix: 0,
1082                    impl_related: true,
1083                }],
1084                conjunct_relations: vec![],
1085                primary_keys: vec![PrimaryKey {
1086                    name: "id".to_owned(),
1087                }],
1088            },
1089            Entity {
1090                table_name: "rust_keyword".to_owned(),
1091                columns: vec![
1092                    Column {
1093                        name: "id".to_owned(),
1094                        col_type: ColumnType::Integer,
1095                        auto_increment: true,
1096                        not_null: true,
1097                        unique: false,
1098                        unique_key: None,
1099                    },
1100                    Column {
1101                        name: "testing".to_owned(),
1102                        col_type: ColumnType::TinyInteger,
1103                        auto_increment: false,
1104                        not_null: true,
1105                        unique: false,
1106                        unique_key: None,
1107                    },
1108                    Column {
1109                        name: "rust".to_owned(),
1110                        col_type: ColumnType::TinyUnsigned,
1111                        auto_increment: false,
1112                        not_null: true,
1113                        unique: false,
1114                        unique_key: None,
1115                    },
1116                    Column {
1117                        name: "keywords".to_owned(),
1118                        col_type: ColumnType::SmallInteger,
1119                        auto_increment: false,
1120                        not_null: true,
1121                        unique: false,
1122                        unique_key: None,
1123                    },
1124                    Column {
1125                        name: "type".to_owned(),
1126                        col_type: ColumnType::SmallUnsigned,
1127                        auto_increment: false,
1128                        not_null: true,
1129                        unique: false,
1130                        unique_key: None,
1131                    },
1132                    Column {
1133                        name: "typeof".to_owned(),
1134                        col_type: ColumnType::Integer,
1135                        auto_increment: false,
1136                        not_null: true,
1137                        unique: false,
1138                        unique_key: None,
1139                    },
1140                    Column {
1141                        name: "crate".to_owned(),
1142                        col_type: ColumnType::Unsigned,
1143                        auto_increment: false,
1144                        not_null: true,
1145                        unique: false,
1146                        unique_key: None,
1147                    },
1148                    Column {
1149                        name: "self".to_owned(),
1150                        col_type: ColumnType::BigInteger,
1151                        auto_increment: false,
1152                        not_null: true,
1153                        unique: false,
1154                        unique_key: None,
1155                    },
1156                    Column {
1157                        name: "self_id1".to_owned(),
1158                        col_type: ColumnType::BigUnsigned,
1159                        auto_increment: false,
1160                        not_null: true,
1161                        unique: false,
1162                        unique_key: None,
1163                    },
1164                    Column {
1165                        name: "self_id2".to_owned(),
1166                        col_type: ColumnType::Integer,
1167                        auto_increment: false,
1168                        not_null: true,
1169                        unique: false,
1170                        unique_key: None,
1171                    },
1172                    Column {
1173                        name: "fruit_id1".to_owned(),
1174                        col_type: ColumnType::Integer,
1175                        auto_increment: false,
1176                        not_null: true,
1177                        unique: false,
1178                        unique_key: None,
1179                    },
1180                    Column {
1181                        name: "fruit_id2".to_owned(),
1182                        col_type: ColumnType::Integer,
1183                        auto_increment: false,
1184                        not_null: true,
1185                        unique: false,
1186                        unique_key: None,
1187                    },
1188                    Column {
1189                        name: "cake_id".to_owned(),
1190                        col_type: ColumnType::Integer,
1191                        auto_increment: false,
1192                        not_null: true,
1193                        unique: false,
1194                        unique_key: None,
1195                    },
1196                ],
1197                relations: vec![
1198                    Relation {
1199                        ref_table: "rust_keyword".to_owned(),
1200                        columns: vec!["self_id1".to_owned()],
1201                        ref_columns: vec!["id".to_owned()],
1202                        rel_type: RelationType::BelongsTo,
1203                        on_delete: None,
1204                        on_update: None,
1205                        self_referencing: true,
1206                        num_suffix: 1,
1207                        impl_related: true,
1208                    },
1209                    Relation {
1210                        ref_table: "rust_keyword".to_owned(),
1211                        columns: vec!["self_id2".to_owned()],
1212                        ref_columns: vec!["id".to_owned()],
1213                        rel_type: RelationType::BelongsTo,
1214                        on_delete: None,
1215                        on_update: None,
1216                        self_referencing: true,
1217                        num_suffix: 2,
1218                        impl_related: true,
1219                    },
1220                    Relation {
1221                        ref_table: "fruit".to_owned(),
1222                        columns: vec!["fruit_id1".to_owned()],
1223                        ref_columns: vec!["id".to_owned()],
1224                        rel_type: RelationType::BelongsTo,
1225                        on_delete: None,
1226                        on_update: None,
1227                        self_referencing: false,
1228                        num_suffix: 1,
1229                        impl_related: true,
1230                    },
1231                    Relation {
1232                        ref_table: "fruit".to_owned(),
1233                        columns: vec!["fruit_id2".to_owned()],
1234                        ref_columns: vec!["id".to_owned()],
1235                        rel_type: RelationType::BelongsTo,
1236                        on_delete: None,
1237                        on_update: None,
1238                        self_referencing: false,
1239                        num_suffix: 2,
1240                        impl_related: true,
1241                    },
1242                    Relation {
1243                        ref_table: "cake".to_owned(),
1244                        columns: vec!["cake_id".to_owned()],
1245                        ref_columns: vec!["id".to_owned()],
1246                        rel_type: RelationType::BelongsTo,
1247                        on_delete: None,
1248                        on_update: None,
1249                        self_referencing: false,
1250                        num_suffix: 0,
1251                        impl_related: true,
1252                    },
1253                ],
1254                conjunct_relations: vec![],
1255                primary_keys: vec![PrimaryKey {
1256                    name: "id".to_owned(),
1257                }],
1258            },
1259            Entity {
1260                table_name: "cake_with_float".to_owned(),
1261                columns: vec![
1262                    Column {
1263                        name: "id".to_owned(),
1264                        col_type: ColumnType::Integer,
1265                        auto_increment: true,
1266                        not_null: true,
1267                        unique: false,
1268                        unique_key: None,
1269                    },
1270                    Column {
1271                        name: "name".to_owned(),
1272                        col_type: ColumnType::Text,
1273                        auto_increment: false,
1274                        not_null: false,
1275                        unique: false,
1276                        unique_key: None,
1277                    },
1278                    Column {
1279                        name: "price".to_owned(),
1280                        col_type: ColumnType::Float,
1281                        auto_increment: false,
1282                        not_null: false,
1283                        unique: false,
1284                        unique_key: None,
1285                    },
1286                ],
1287                relations: vec![Relation {
1288                    ref_table: "fruit".to_owned(),
1289                    columns: vec![],
1290                    ref_columns: vec![],
1291                    rel_type: RelationType::HasMany,
1292                    on_delete: None,
1293                    on_update: None,
1294                    self_referencing: false,
1295                    num_suffix: 0,
1296                    impl_related: true,
1297                }],
1298                conjunct_relations: vec![ConjunctRelation {
1299                    via: "cake_filling".to_owned(),
1300                    to: "filling".to_owned(),
1301                }],
1302                primary_keys: vec![PrimaryKey {
1303                    name: "id".to_owned(),
1304                }],
1305            },
1306            Entity {
1307                table_name: "cake_with_double".to_owned(),
1308                columns: vec![
1309                    Column {
1310                        name: "id".to_owned(),
1311                        col_type: ColumnType::Integer,
1312                        auto_increment: true,
1313                        not_null: true,
1314                        unique: false,
1315                        unique_key: None,
1316                    },
1317                    Column {
1318                        name: "name".to_owned(),
1319                        col_type: ColumnType::Text,
1320                        auto_increment: false,
1321                        not_null: false,
1322                        unique: false,
1323                        unique_key: None,
1324                    },
1325                    Column {
1326                        name: "price".to_owned(),
1327                        col_type: ColumnType::Double,
1328                        auto_increment: false,
1329                        not_null: false,
1330                        unique: false,
1331                        unique_key: None,
1332                    },
1333                ],
1334                relations: vec![Relation {
1335                    ref_table: "fruit".to_owned(),
1336                    columns: vec![],
1337                    ref_columns: vec![],
1338                    rel_type: RelationType::HasMany,
1339                    on_delete: None,
1340                    on_update: None,
1341                    self_referencing: false,
1342                    num_suffix: 0,
1343                    impl_related: true,
1344                }],
1345                conjunct_relations: vec![ConjunctRelation {
1346                    via: "cake_filling".to_owned(),
1347                    to: "filling".to_owned(),
1348                }],
1349                primary_keys: vec![PrimaryKey {
1350                    name: "id".to_owned(),
1351                }],
1352            },
1353            Entity {
1354                table_name: "collection".to_owned(),
1355                columns: vec![
1356                    Column {
1357                        name: "id".to_owned(),
1358                        col_type: ColumnType::Integer,
1359                        auto_increment: true,
1360                        not_null: true,
1361                        unique: false,
1362                        unique_key: None,
1363                    },
1364                    Column {
1365                        name: "integers".to_owned(),
1366                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Integer)),
1367                        auto_increment: false,
1368                        not_null: true,
1369                        unique: false,
1370                        unique_key: None,
1371                    },
1372                    Column {
1373                        name: "integers_opt".to_owned(),
1374                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Integer)),
1375                        auto_increment: false,
1376                        not_null: false,
1377                        unique: false,
1378                        unique_key: None,
1379                    },
1380                ],
1381                relations: vec![],
1382                conjunct_relations: vec![],
1383                primary_keys: vec![PrimaryKey {
1384                    name: "id".to_owned(),
1385                }],
1386            },
1387            Entity {
1388                table_name: "collection_float".to_owned(),
1389                columns: vec![
1390                    Column {
1391                        name: "id".to_owned(),
1392                        col_type: ColumnType::Integer,
1393                        auto_increment: true,
1394                        not_null: true,
1395                        unique: false,
1396                        unique_key: None,
1397                    },
1398                    Column {
1399                        name: "floats".to_owned(),
1400                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Float)),
1401                        auto_increment: false,
1402                        not_null: true,
1403                        unique: false,
1404                        unique_key: None,
1405                    },
1406                    Column {
1407                        name: "doubles".to_owned(),
1408                        col_type: ColumnType::Array(RcOrArc::new(ColumnType::Double)),
1409                        auto_increment: false,
1410                        not_null: true,
1411                        unique: false,
1412                        unique_key: None,
1413                    },
1414                ],
1415                relations: vec![],
1416                conjunct_relations: vec![],
1417                primary_keys: vec![PrimaryKey {
1418                    name: "id".to_owned(),
1419                }],
1420            },
1421            Entity {
1422                table_name: "parent".to_owned(),
1423                columns: vec![
1424                    Column {
1425                        name: "id1".to_owned(),
1426                        col_type: ColumnType::Integer,
1427                        auto_increment: false,
1428                        not_null: true,
1429                        unique: false,
1430                        unique_key: None,
1431                    },
1432                    Column {
1433                        name: "id2".to_owned(),
1434                        col_type: ColumnType::Integer,
1435                        auto_increment: false,
1436                        not_null: true,
1437                        unique: false,
1438                        unique_key: None,
1439                    },
1440                ],
1441                relations: vec![Relation {
1442                    ref_table: "child".to_owned(),
1443                    columns: vec![],
1444                    ref_columns: vec![],
1445                    rel_type: RelationType::HasMany,
1446                    on_delete: None,
1447                    on_update: None,
1448                    self_referencing: false,
1449                    num_suffix: 0,
1450                    impl_related: true,
1451                }],
1452                conjunct_relations: vec![],
1453                primary_keys: vec![
1454                    PrimaryKey {
1455                        name: "id1".to_owned(),
1456                    },
1457                    PrimaryKey {
1458                        name: "id2".to_owned(),
1459                    },
1460                ],
1461            },
1462            Entity {
1463                table_name: "child".to_owned(),
1464                columns: vec![
1465                    Column {
1466                        name: "id".to_owned(),
1467                        col_type: ColumnType::Integer,
1468                        auto_increment: true,
1469                        not_null: true,
1470                        unique: false,
1471                        unique_key: None,
1472                    },
1473                    Column {
1474                        name: "parent_id1".to_owned(),
1475                        col_type: ColumnType::Integer,
1476                        auto_increment: false,
1477                        not_null: true,
1478                        unique: false,
1479                        unique_key: None,
1480                    },
1481                    Column {
1482                        name: "parent_id2".to_owned(),
1483                        col_type: ColumnType::Integer,
1484                        auto_increment: false,
1485                        not_null: true,
1486                        unique: false,
1487                        unique_key: None,
1488                    },
1489                ],
1490                relations: vec![Relation {
1491                    ref_table: "parent".to_owned(),
1492                    columns: vec!["parent_id1".to_owned(), "parent_id2".to_owned()],
1493                    ref_columns: vec!["id1".to_owned(), "id2".to_owned()],
1494                    rel_type: RelationType::BelongsTo,
1495                    on_delete: None,
1496                    on_update: None,
1497                    self_referencing: false,
1498                    num_suffix: 0,
1499                    impl_related: true,
1500                }],
1501                conjunct_relations: vec![],
1502                primary_keys: vec![PrimaryKey {
1503                    name: "id".to_owned(),
1504                }],
1505            },
1506        ]
1507    }
1508
1509    fn parse_from_file<R>(inner: R) -> io::Result<TokenStream>
1510    where
1511        R: Read,
1512    {
1513        let mut reader = BufReader::new(inner);
1514        let mut lines: Vec<String> = Vec::new();
1515
1516        reader.read_until(b';', &mut Vec::new())?;
1517
1518        let mut line = String::new();
1519        while reader.read_line(&mut line)? > 0 {
1520            lines.push(line.to_owned());
1521            line.clear();
1522        }
1523        let content = lines.join("");
1524        Ok(content.parse().unwrap())
1525    }
1526
1527    fn parse_from_frontend_file<R>(inner: R) -> io::Result<TokenStream>
1528    where
1529        R: Read,
1530    {
1531        let mut reader = BufReader::new(inner);
1532        let mut lines: Vec<String> = Vec::new();
1533
1534        reader.read_until(b'\n', &mut Vec::new())?;
1535
1536        let mut line = String::new();
1537        while reader.read_line(&mut line)? > 0 {
1538            lines.push(line.to_owned());
1539            line.clear();
1540        }
1541        let content = lines.join("");
1542        Ok(content.parse().unwrap())
1543    }
1544
1545    #[test]
1546    fn test_gen_expanded_code_blocks() -> io::Result<()> {
1547        let entities = setup();
1548        const ENTITY_FILES: [&str; 13] = [
1549            include_str!("../../tests/expanded/cake.rs"),
1550            include_str!("../../tests/expanded/cake_filling.rs"),
1551            include_str!("../../tests/expanded/cake_filling_price.rs"),
1552            include_str!("../../tests/expanded/filling.rs"),
1553            include_str!("../../tests/expanded/fruit.rs"),
1554            include_str!("../../tests/expanded/vendor.rs"),
1555            include_str!("../../tests/expanded/rust_keyword.rs"),
1556            include_str!("../../tests/expanded/cake_with_float.rs"),
1557            include_str!("../../tests/expanded/cake_with_double.rs"),
1558            include_str!("../../tests/expanded/collection.rs"),
1559            include_str!("../../tests/expanded/collection_float.rs"),
1560            include_str!("../../tests/expanded/parent.rs"),
1561            include_str!("../../tests/expanded/child.rs"),
1562        ];
1563        const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [
1564            include_str!("../../tests/expanded_with_schema_name/cake.rs"),
1565            include_str!("../../tests/expanded_with_schema_name/cake_filling.rs"),
1566            include_str!("../../tests/expanded_with_schema_name/cake_filling_price.rs"),
1567            include_str!("../../tests/expanded_with_schema_name/filling.rs"),
1568            include_str!("../../tests/expanded_with_schema_name/fruit.rs"),
1569            include_str!("../../tests/expanded_with_schema_name/vendor.rs"),
1570            include_str!("../../tests/expanded_with_schema_name/rust_keyword.rs"),
1571            include_str!("../../tests/expanded_with_schema_name/cake_with_float.rs"),
1572            include_str!("../../tests/expanded_with_schema_name/cake_with_double.rs"),
1573            include_str!("../../tests/expanded_with_schema_name/collection.rs"),
1574            include_str!("../../tests/expanded_with_schema_name/collection_float.rs"),
1575            include_str!("../../tests/expanded_with_schema_name/parent.rs"),
1576            include_str!("../../tests/expanded_with_schema_name/child.rs"),
1577        ];
1578
1579        assert_eq!(entities.len(), ENTITY_FILES.len());
1580
1581        for (i, entity) in entities.iter().enumerate() {
1582            assert_eq!(
1583                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
1584                EntityWriter::gen_expanded_code_blocks(
1585                    entity,
1586                    &crate::WithSerde::None,
1587                    &crate::DateTimeCrate::Chrono,
1588                    &None,
1589                    false,
1590                    false,
1591                    &TokenStream::new(),
1592                    &TokenStream::new(),
1593                    &TokenStream::new(),
1594                    false,
1595                    true,
1596                )
1597                .into_iter()
1598                .skip(1)
1599                .fold(TokenStream::new(), |mut acc, tok| {
1600                    acc.extend(tok);
1601                    acc
1602                })
1603                .to_string()
1604            );
1605            assert_eq!(
1606                parse_from_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(),
1607                EntityWriter::gen_expanded_code_blocks(
1608                    entity,
1609                    &crate::WithSerde::None,
1610                    &crate::DateTimeCrate::Chrono,
1611                    &Some("schema_name".to_owned()),
1612                    false,
1613                    false,
1614                    &TokenStream::new(),
1615                    &TokenStream::new(),
1616                    &TokenStream::new(),
1617                    false,
1618                    true,
1619                )
1620                .into_iter()
1621                .skip(1)
1622                .fold(TokenStream::new(), |mut acc, tok| {
1623                    acc.extend(tok);
1624                    acc
1625                })
1626                .to_string()
1627            );
1628        }
1629
1630        Ok(())
1631    }
1632
1633    #[test]
1634    fn test_gen_compact_code_blocks() -> io::Result<()> {
1635        let entities = setup();
1636        const ENTITY_FILES: [&str; 13] = [
1637            include_str!("../../tests/compact/cake.rs"),
1638            include_str!("../../tests/compact/cake_filling.rs"),
1639            include_str!("../../tests/compact/cake_filling_price.rs"),
1640            include_str!("../../tests/compact/filling.rs"),
1641            include_str!("../../tests/compact/fruit.rs"),
1642            include_str!("../../tests/compact/vendor.rs"),
1643            include_str!("../../tests/compact/rust_keyword.rs"),
1644            include_str!("../../tests/compact/cake_with_float.rs"),
1645            include_str!("../../tests/compact/cake_with_double.rs"),
1646            include_str!("../../tests/compact/collection.rs"),
1647            include_str!("../../tests/compact/collection_float.rs"),
1648            include_str!("../../tests/compact/parent.rs"),
1649            include_str!("../../tests/compact/child.rs"),
1650        ];
1651        const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [
1652            include_str!("../../tests/compact_with_schema_name/cake.rs"),
1653            include_str!("../../tests/compact_with_schema_name/cake_filling.rs"),
1654            include_str!("../../tests/compact_with_schema_name/cake_filling_price.rs"),
1655            include_str!("../../tests/compact_with_schema_name/filling.rs"),
1656            include_str!("../../tests/compact_with_schema_name/fruit.rs"),
1657            include_str!("../../tests/compact_with_schema_name/vendor.rs"),
1658            include_str!("../../tests/compact_with_schema_name/rust_keyword.rs"),
1659            include_str!("../../tests/compact_with_schema_name/cake_with_float.rs"),
1660            include_str!("../../tests/compact_with_schema_name/cake_with_double.rs"),
1661            include_str!("../../tests/compact_with_schema_name/collection.rs"),
1662            include_str!("../../tests/compact_with_schema_name/collection_float.rs"),
1663            include_str!("../../tests/compact_with_schema_name/parent.rs"),
1664            include_str!("../../tests/compact_with_schema_name/child.rs"),
1665        ];
1666
1667        assert_eq!(entities.len(), ENTITY_FILES.len());
1668
1669        for (i, entity) in entities.iter().enumerate() {
1670            assert_eq!(
1671                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
1672                EntityWriter::gen_compact_code_blocks(
1673                    entity,
1674                    &crate::WithSerde::None,
1675                    &crate::DateTimeCrate::Chrono,
1676                    &None,
1677                    false,
1678                    false,
1679                    &TokenStream::new(),
1680                    &TokenStream::new(),
1681                    &TokenStream::new(),
1682                    false,
1683                    true,
1684                )
1685                .into_iter()
1686                .skip(1)
1687                .fold(TokenStream::new(), |mut acc, tok| {
1688                    acc.extend(tok);
1689                    acc
1690                })
1691                .to_string()
1692            );
1693            assert_eq!(
1694                parse_from_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(),
1695                EntityWriter::gen_compact_code_blocks(
1696                    entity,
1697                    &crate::WithSerde::None,
1698                    &crate::DateTimeCrate::Chrono,
1699                    &Some("schema_name".to_owned()),
1700                    false,
1701                    false,
1702                    &TokenStream::new(),
1703                    &TokenStream::new(),
1704                    &TokenStream::new(),
1705                    false,
1706                    true,
1707                )
1708                .into_iter()
1709                .skip(1)
1710                .fold(TokenStream::new(), |mut acc, tok| {
1711                    acc.extend(tok);
1712                    acc
1713                })
1714                .to_string()
1715            );
1716        }
1717
1718        Ok(())
1719    }
1720
1721    #[test]
1722    fn test_gen_frontend_code_blocks() -> io::Result<()> {
1723        let entities = setup();
1724        const ENTITY_FILES: [&str; 13] = [
1725            include_str!("../../tests/frontend/cake.rs"),
1726            include_str!("../../tests/frontend/cake_filling.rs"),
1727            include_str!("../../tests/frontend/cake_filling_price.rs"),
1728            include_str!("../../tests/frontend/filling.rs"),
1729            include_str!("../../tests/frontend/fruit.rs"),
1730            include_str!("../../tests/frontend/vendor.rs"),
1731            include_str!("../../tests/frontend/rust_keyword.rs"),
1732            include_str!("../../tests/frontend/cake_with_float.rs"),
1733            include_str!("../../tests/frontend/cake_with_double.rs"),
1734            include_str!("../../tests/frontend/collection.rs"),
1735            include_str!("../../tests/frontend/collection_float.rs"),
1736            include_str!("../../tests/frontend/parent.rs"),
1737            include_str!("../../tests/frontend/child.rs"),
1738        ];
1739        const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [
1740            include_str!("../../tests/frontend_with_schema_name/cake.rs"),
1741            include_str!("../../tests/frontend_with_schema_name/cake_filling.rs"),
1742            include_str!("../../tests/frontend_with_schema_name/cake_filling_price.rs"),
1743            include_str!("../../tests/frontend_with_schema_name/filling.rs"),
1744            include_str!("../../tests/frontend_with_schema_name/fruit.rs"),
1745            include_str!("../../tests/frontend_with_schema_name/vendor.rs"),
1746            include_str!("../../tests/frontend_with_schema_name/rust_keyword.rs"),
1747            include_str!("../../tests/frontend_with_schema_name/cake_with_float.rs"),
1748            include_str!("../../tests/frontend_with_schema_name/cake_with_double.rs"),
1749            include_str!("../../tests/frontend_with_schema_name/collection.rs"),
1750            include_str!("../../tests/frontend_with_schema_name/collection_float.rs"),
1751            include_str!("../../tests/frontend_with_schema_name/parent.rs"),
1752            include_str!("../../tests/frontend_with_schema_name/child.rs"),
1753        ];
1754
1755        assert_eq!(entities.len(), ENTITY_FILES.len());
1756
1757        for (i, entity) in entities.iter().enumerate() {
1758            assert_eq!(
1759                dbg!(parse_from_frontend_file(ENTITY_FILES[i].as_bytes())?.to_string()),
1760                EntityWriter::gen_frontend_code_blocks(
1761                    entity,
1762                    &crate::WithSerde::None,
1763                    &crate::DateTimeCrate::Chrono,
1764                    &None,
1765                    false,
1766                    false,
1767                    &TokenStream::new(),
1768                    &TokenStream::new(),
1769                    &TokenStream::new(),
1770                    false,
1771                    true,
1772                )
1773                .into_iter()
1774                .skip(1)
1775                .fold(TokenStream::new(), |mut acc, tok| {
1776                    acc.extend(tok);
1777                    acc
1778                })
1779                .to_string()
1780            );
1781            assert_eq!(
1782                parse_from_frontend_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(),
1783                EntityWriter::gen_frontend_code_blocks(
1784                    entity,
1785                    &crate::WithSerde::None,
1786                    &crate::DateTimeCrate::Chrono,
1787                    &Some("schema_name".to_owned()),
1788                    false,
1789                    false,
1790                    &TokenStream::new(),
1791                    &TokenStream::new(),
1792                    &TokenStream::new(),
1793                    false,
1794                    true,
1795                )
1796                .into_iter()
1797                .skip(1)
1798                .fold(TokenStream::new(), |mut acc, tok| {
1799                    acc.extend(tok);
1800                    acc
1801                })
1802                .to_string()
1803            );
1804        }
1805
1806        Ok(())
1807    }
1808
1809    #[test]
1810    fn test_gen_with_serde() -> io::Result<()> {
1811        let cake_entity = setup().get(0).unwrap().clone();
1812
1813        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
1814
1815        // Compact code blocks
1816        assert_eq!(
1817            comparable_file_string(include_str!("../../tests/compact_with_serde/cake_none.rs"))?,
1818            generated_to_string(EntityWriter::gen_compact_code_blocks(
1819                &cake_entity,
1820                &WithSerde::None,
1821                &DateTimeCrate::Chrono,
1822                &None,
1823                false,
1824                false,
1825                &TokenStream::new(),
1826                &TokenStream::new(),
1827                &TokenStream::new(),
1828                false,
1829                true,
1830            ))
1831        );
1832        assert_eq!(
1833            comparable_file_string(include_str!(
1834                "../../tests/compact_with_serde/cake_serialize.rs"
1835            ))?,
1836            generated_to_string(EntityWriter::gen_compact_code_blocks(
1837                &cake_entity,
1838                &WithSerde::Serialize,
1839                &DateTimeCrate::Chrono,
1840                &None,
1841                false,
1842                false,
1843                &TokenStream::new(),
1844                &TokenStream::new(),
1845                &TokenStream::new(),
1846                false,
1847                true,
1848            ))
1849        );
1850        assert_eq!(
1851            comparable_file_string(include_str!(
1852                "../../tests/compact_with_serde/cake_deserialize.rs"
1853            ))?,
1854            generated_to_string(EntityWriter::gen_compact_code_blocks(
1855                &cake_entity,
1856                &WithSerde::Deserialize,
1857                &DateTimeCrate::Chrono,
1858                &None,
1859                true,
1860                false,
1861                &TokenStream::new(),
1862                &TokenStream::new(),
1863                &TokenStream::new(),
1864                false,
1865                true,
1866            ))
1867        );
1868        assert_eq!(
1869            comparable_file_string(include_str!("../../tests/compact_with_serde/cake_both.rs"))?,
1870            generated_to_string(EntityWriter::gen_compact_code_blocks(
1871                &cake_entity,
1872                &WithSerde::Both,
1873                &DateTimeCrate::Chrono,
1874                &None,
1875                true,
1876                false,
1877                &TokenStream::new(),
1878                &TokenStream::new(),
1879                &TokenStream::new(),
1880                false,
1881                true,
1882            ))
1883        );
1884
1885        // Expanded code blocks
1886        assert_eq!(
1887            comparable_file_string(include_str!("../../tests/expanded_with_serde/cake_none.rs"))?,
1888            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1889                &cake_entity,
1890                &WithSerde::None,
1891                &DateTimeCrate::Chrono,
1892                &None,
1893                false,
1894                false,
1895                &TokenStream::new(),
1896                &TokenStream::new(),
1897                &TokenStream::new(),
1898                false,
1899                true,
1900            ))
1901        );
1902        assert_eq!(
1903            comparable_file_string(include_str!(
1904                "../../tests/expanded_with_serde/cake_serialize.rs"
1905            ))?,
1906            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1907                &cake_entity,
1908                &WithSerde::Serialize,
1909                &DateTimeCrate::Chrono,
1910                &None,
1911                false,
1912                false,
1913                &TokenStream::new(),
1914                &TokenStream::new(),
1915                &TokenStream::new(),
1916                false,
1917                true,
1918            ))
1919        );
1920        assert_eq!(
1921            comparable_file_string(include_str!(
1922                "../../tests/expanded_with_serde/cake_deserialize.rs"
1923            ))?,
1924            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1925                &cake_entity,
1926                &WithSerde::Deserialize,
1927                &DateTimeCrate::Chrono,
1928                &None,
1929                true,
1930                false,
1931                &TokenStream::new(),
1932                &TokenStream::new(),
1933                &TokenStream::new(),
1934                false,
1935                true,
1936            ))
1937        );
1938        assert_eq!(
1939            comparable_file_string(include_str!("../../tests/expanded_with_serde/cake_both.rs"))?,
1940            generated_to_string(EntityWriter::gen_expanded_code_blocks(
1941                &cake_entity,
1942                &WithSerde::Both,
1943                &DateTimeCrate::Chrono,
1944                &None,
1945                true,
1946                false,
1947                &TokenStream::new(),
1948                &TokenStream::new(),
1949                &TokenStream::new(),
1950                false,
1951                true,
1952            ))
1953        );
1954
1955        // Frontend code blocks
1956        assert_eq!(
1957            comparable_file_string(include_str!("../../tests/frontend_with_serde/cake_none.rs"))?,
1958            generated_to_string(EntityWriter::gen_frontend_code_blocks(
1959                &cake_entity,
1960                &WithSerde::None,
1961                &DateTimeCrate::Chrono,
1962                &None,
1963                false,
1964                false,
1965                &TokenStream::new(),
1966                &TokenStream::new(),
1967                &TokenStream::new(),
1968                false,
1969                true,
1970            ))
1971        );
1972        assert_eq!(
1973            comparable_file_string(include_str!(
1974                "../../tests/frontend_with_serde/cake_serialize.rs"
1975            ))?,
1976            generated_to_string(EntityWriter::gen_frontend_code_blocks(
1977                &cake_entity,
1978                &WithSerde::Serialize,
1979                &DateTimeCrate::Chrono,
1980                &None,
1981                false,
1982                false,
1983                &TokenStream::new(),
1984                &TokenStream::new(),
1985                &TokenStream::new(),
1986                false,
1987                true,
1988            ))
1989        );
1990        assert_eq!(
1991            comparable_file_string(include_str!(
1992                "../../tests/frontend_with_serde/cake_deserialize.rs"
1993            ))?,
1994            generated_to_string(EntityWriter::gen_frontend_code_blocks(
1995                &cake_entity,
1996                &WithSerde::Deserialize,
1997                &DateTimeCrate::Chrono,
1998                &None,
1999                true,
2000                false,
2001                &TokenStream::new(),
2002                &TokenStream::new(),
2003                &TokenStream::new(),
2004                false,
2005                true,
2006            ))
2007        );
2008        assert_eq!(
2009            comparable_file_string(include_str!("../../tests/frontend_with_serde/cake_both.rs"))?,
2010            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2011                &cake_entity,
2012                &WithSerde::Both,
2013                &DateTimeCrate::Chrono,
2014                &None,
2015                true,
2016                false,
2017                &TokenStream::new(),
2018                &TokenStream::new(),
2019                &TokenStream::new(),
2020                false,
2021                true,
2022            ))
2023        );
2024
2025        Ok(())
2026    }
2027
2028    #[test]
2029    fn test_gen_with_seaography() -> io::Result<()> {
2030        let cake_entity = Entity {
2031            table_name: "cake".to_owned(),
2032            columns: vec![
2033                Column {
2034                    name: "id".to_owned(),
2035                    col_type: ColumnType::Integer,
2036                    auto_increment: true,
2037                    not_null: true,
2038                    unique: false,
2039                    unique_key: None,
2040                },
2041                Column {
2042                    name: "name".to_owned(),
2043                    col_type: ColumnType::Text,
2044                    auto_increment: false,
2045                    not_null: false,
2046                    unique: false,
2047                    unique_key: None,
2048                },
2049                Column {
2050                    name: "base_id".to_owned(),
2051                    col_type: ColumnType::Integer,
2052                    auto_increment: false,
2053                    not_null: false,
2054                    unique: false,
2055                    unique_key: None,
2056                },
2057            ],
2058            relations: vec![
2059                Relation {
2060                    ref_table: "fruit".to_owned(),
2061                    columns: vec![],
2062                    ref_columns: vec![],
2063                    rel_type: RelationType::HasMany,
2064                    on_delete: None,
2065                    on_update: None,
2066                    self_referencing: false,
2067                    num_suffix: 0,
2068                    impl_related: true,
2069                },
2070                Relation {
2071                    ref_table: "cake".to_owned(),
2072                    columns: vec![],
2073                    ref_columns: vec![],
2074                    rel_type: RelationType::HasOne,
2075                    on_delete: None,
2076                    on_update: None,
2077                    self_referencing: true,
2078                    num_suffix: 0,
2079                    impl_related: true,
2080                },
2081            ],
2082            conjunct_relations: vec![ConjunctRelation {
2083                via: "cake_filling".to_owned(),
2084                to: "filling".to_owned(),
2085            }],
2086            primary_keys: vec![PrimaryKey {
2087                name: "id".to_owned(),
2088            }],
2089        };
2090
2091        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2092
2093        // Compact code blocks
2094        assert_eq!(
2095            comparable_file_string(include_str!("../../tests/with_seaography/cake.rs"))?,
2096            generated_to_string(EntityWriter::gen_compact_code_blocks(
2097                &cake_entity,
2098                &WithSerde::None,
2099                &DateTimeCrate::Chrono,
2100                &None,
2101                false,
2102                false,
2103                &TokenStream::new(),
2104                &TokenStream::new(),
2105                &TokenStream::new(),
2106                true,
2107                true,
2108            ))
2109        );
2110
2111        // Expanded code blocks
2112        assert_eq!(
2113            comparable_file_string(include_str!("../../tests/with_seaography/cake_expanded.rs"))?,
2114            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2115                &cake_entity,
2116                &WithSerde::None,
2117                &DateTimeCrate::Chrono,
2118                &None,
2119                false,
2120                false,
2121                &TokenStream::new(),
2122                &TokenStream::new(),
2123                &TokenStream::new(),
2124                true,
2125                true,
2126            ))
2127        );
2128
2129        // Frontend code blocks
2130        assert_eq!(
2131            comparable_file_string(include_str!("../../tests/with_seaography/cake_frontend.rs"))?,
2132            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2133                &cake_entity,
2134                &WithSerde::None,
2135                &DateTimeCrate::Chrono,
2136                &None,
2137                false,
2138                false,
2139                &TokenStream::new(),
2140                &TokenStream::new(),
2141                &TokenStream::new(),
2142                true,
2143                true,
2144            ))
2145        );
2146
2147        Ok(())
2148    }
2149
2150    #[test]
2151    fn test_gen_with_seaography_mod() -> io::Result<()> {
2152        use crate::ActiveEnum;
2153        use sea_query::IntoIden;
2154
2155        let entities = setup();
2156        let enums = vec![
2157            (
2158                "coinflip_result_type",
2159                ActiveEnum {
2160                    enum_name: Alias::new("coinflip_result_type").into_iden(),
2161                    values: vec!["HEADS", "TAILS"]
2162                        .into_iter()
2163                        .map(|variant| Alias::new(variant).into_iden())
2164                        .collect(),
2165                },
2166            ),
2167            (
2168                "media_type",
2169                ActiveEnum {
2170                    enum_name: Alias::new("media_type").into_iden(),
2171                    values: vec![
2172                        "UNKNOWN",
2173                        "BITMAP",
2174                        "DRAWING",
2175                        "AUDIO",
2176                        "VIDEO",
2177                        "MULTIMEDIA",
2178                        "OFFICE",
2179                        "TEXT",
2180                        "EXECUTABLE",
2181                        "ARCHIVE",
2182                        "3D",
2183                    ]
2184                    .into_iter()
2185                    .map(|variant| Alias::new(variant).into_iden())
2186                    .collect(),
2187                },
2188            ),
2189        ]
2190        .into_iter()
2191        .map(|(k, v)| (k.to_string(), v))
2192        .collect();
2193
2194        assert_eq!(
2195            comparable_file_string(include_str!("../../tests/with_seaography/mod.rs"))?,
2196            generated_to_string(vec![EntityWriter::gen_seaography_entity_mod(
2197                &entities, &enums,
2198            )])
2199        );
2200
2201        Ok(())
2202    }
2203
2204    #[test]
2205    fn test_gen_with_derives() -> io::Result<()> {
2206        let mut cake_entity = setup().get_mut(0).unwrap().clone();
2207
2208        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2209
2210        // Compact code blocks
2211        assert_eq!(
2212            comparable_file_string(include_str!(
2213                "../../tests/compact_with_derives/cake_none.rs"
2214            ))?,
2215            generated_to_string(EntityWriter::gen_compact_code_blocks(
2216                &cake_entity,
2217                &WithSerde::None,
2218                &DateTimeCrate::Chrono,
2219                &None,
2220                false,
2221                false,
2222                &TokenStream::new(),
2223                &TokenStream::new(),
2224                &TokenStream::new(),
2225                false,
2226                true,
2227            ))
2228        );
2229        assert_eq!(
2230            comparable_file_string(include_str!("../../tests/compact_with_derives/cake_one.rs"))?,
2231            generated_to_string(EntityWriter::gen_compact_code_blocks(
2232                &cake_entity,
2233                &WithSerde::None,
2234                &DateTimeCrate::Chrono,
2235                &None,
2236                false,
2237                false,
2238                &bonus_derive(["ts_rs::TS"]),
2239                &TokenStream::new(),
2240                &TokenStream::new(),
2241                false,
2242                true,
2243            ))
2244        );
2245        assert_eq!(
2246            comparable_file_string(include_str!(
2247                "../../tests/compact_with_derives/cake_multiple.rs"
2248            ))?,
2249            generated_to_string(EntityWriter::gen_compact_code_blocks(
2250                &cake_entity,
2251                &WithSerde::None,
2252                &DateTimeCrate::Chrono,
2253                &None,
2254                false,
2255                false,
2256                &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]),
2257                &TokenStream::new(),
2258                &TokenStream::new(),
2259                false,
2260                true,
2261            ))
2262        );
2263
2264        // Expanded code blocks
2265        assert_eq!(
2266            comparable_file_string(include_str!(
2267                "../../tests/expanded_with_derives/cake_none.rs"
2268            ))?,
2269            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2270                &cake_entity,
2271                &WithSerde::None,
2272                &DateTimeCrate::Chrono,
2273                &None,
2274                false,
2275                false,
2276                &TokenStream::new(),
2277                &TokenStream::new(),
2278                &TokenStream::new(),
2279                false,
2280                true,
2281            ))
2282        );
2283        assert_eq!(
2284            comparable_file_string(include_str!(
2285                "../../tests/expanded_with_derives/cake_one.rs"
2286            ))?,
2287            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2288                &cake_entity,
2289                &WithSerde::None,
2290                &DateTimeCrate::Chrono,
2291                &None,
2292                false,
2293                false,
2294                &bonus_derive(["ts_rs::TS"]),
2295                &TokenStream::new(),
2296                &TokenStream::new(),
2297                false,
2298                true,
2299            ))
2300        );
2301        assert_eq!(
2302            comparable_file_string(include_str!(
2303                "../../tests/expanded_with_derives/cake_multiple.rs"
2304            ))?,
2305            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2306                &cake_entity,
2307                &WithSerde::None,
2308                &DateTimeCrate::Chrono,
2309                &None,
2310                false,
2311                false,
2312                &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]),
2313                &TokenStream::new(),
2314                &TokenStream::new(),
2315                false,
2316                true,
2317            ))
2318        );
2319
2320        // Frontend code blocks
2321        assert_eq!(
2322            comparable_file_string(include_str!(
2323                "../../tests/frontend_with_derives/cake_none.rs"
2324            ))?,
2325            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2326                &cake_entity,
2327                &WithSerde::None,
2328                &DateTimeCrate::Chrono,
2329                &None,
2330                false,
2331                false,
2332                &TokenStream::new(),
2333                &TokenStream::new(),
2334                &TokenStream::new(),
2335                false,
2336                true,
2337            ))
2338        );
2339        assert_eq!(
2340            comparable_file_string(include_str!(
2341                "../../tests/frontend_with_derives/cake_one.rs"
2342            ))?,
2343            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2344                &cake_entity,
2345                &WithSerde::None,
2346                &DateTimeCrate::Chrono,
2347                &None,
2348                false,
2349                false,
2350                &bonus_derive(["ts_rs::TS"]),
2351                &TokenStream::new(),
2352                &TokenStream::new(),
2353                false,
2354                true,
2355            ))
2356        );
2357        assert_eq!(
2358            comparable_file_string(include_str!(
2359                "../../tests/frontend_with_derives/cake_multiple.rs"
2360            ))?,
2361            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2362                &cake_entity,
2363                &WithSerde::None,
2364                &DateTimeCrate::Chrono,
2365                &None,
2366                false,
2367                false,
2368                &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]),
2369                &TokenStream::new(),
2370                &TokenStream::new(),
2371                false,
2372                true,
2373            ))
2374        );
2375
2376        // Make the `name` column of `cake` entity as hidden column
2377        cake_entity.columns[1].name = "_name".into();
2378
2379        assert_serde_variant_results(
2380            &cake_entity,
2381            &(
2382                include_str!("../../tests/compact_with_serde/cake_serialize_with_hidden_column.rs"),
2383                WithSerde::Serialize,
2384                None,
2385            ),
2386            Box::new(EntityWriter::gen_compact_code_blocks),
2387        )?;
2388        assert_serde_variant_results(
2389            &cake_entity,
2390            &(
2391                include_str!(
2392                    "../../tests/expanded_with_serde/cake_serialize_with_hidden_column.rs"
2393                ),
2394                WithSerde::Serialize,
2395                None,
2396            ),
2397            Box::new(EntityWriter::gen_expanded_code_blocks),
2398        )?;
2399        assert_serde_variant_results(
2400            &cake_entity,
2401            &(
2402                include_str!(
2403                    "../../tests/frontend_with_serde/cake_serialize_with_hidden_column.rs"
2404                ),
2405                WithSerde::Serialize,
2406                None,
2407            ),
2408            Box::new(EntityWriter::gen_frontend_code_blocks),
2409        )?;
2410
2411        Ok(())
2412    }
2413
2414    #[test]
2415    fn test_gen_with_column_derives() -> io::Result<()> {
2416        let cake_entity = setup().get_mut(0).unwrap().clone();
2417
2418        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2419
2420        assert_eq!(
2421            comparable_file_string(include_str!(
2422                "../../tests/expanded_with_column_derives/cake_one.rs"
2423            ))?,
2424            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2425                &cake_entity,
2426                &WithSerde::None,
2427                &DateTimeCrate::Chrono,
2428                &None,
2429                false,
2430                false,
2431                &TokenStream::new(),
2432                &TokenStream::new(),
2433                &bonus_derive(["async_graphql::Enum"]),
2434                false,
2435                true,
2436            ))
2437        );
2438        assert_eq!(
2439            comparable_file_string(include_str!(
2440                "../../tests/expanded_with_column_derives/cake_multiple.rs"
2441            ))?,
2442            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2443                &cake_entity,
2444                &WithSerde::None,
2445                &DateTimeCrate::Chrono,
2446                &None,
2447                false,
2448                false,
2449                &TokenStream::new(),
2450                &TokenStream::new(),
2451                &bonus_derive(["async_graphql::Enum", "Eq", "PartialEq"]),
2452                false,
2453                true,
2454            ))
2455        );
2456
2457        Ok(())
2458    }
2459
2460    #[allow(clippy::type_complexity)]
2461    fn assert_serde_variant_results(
2462        cake_entity: &Entity,
2463        entity_serde_variant: &(&str, WithSerde, Option<String>),
2464        generator: Box<
2465            dyn Fn(
2466                &Entity,
2467                &WithSerde,
2468                &DateTimeCrate,
2469                &Option<String>,
2470                bool,
2471                bool,
2472                &TokenStream,
2473                &TokenStream,
2474                &TokenStream,
2475                bool,
2476                bool,
2477            ) -> Vec<TokenStream>,
2478        >,
2479    ) -> io::Result<()> {
2480        let mut reader = BufReader::new(entity_serde_variant.0.as_bytes());
2481        let mut lines: Vec<String> = Vec::new();
2482        let serde_skip_deserializing_primary_key = matches!(
2483            entity_serde_variant.1,
2484            WithSerde::Both | WithSerde::Deserialize
2485        );
2486        let serde_skip_hidden_column = matches!(entity_serde_variant.1, WithSerde::Serialize);
2487
2488        reader.read_until(b'\n', &mut Vec::new())?;
2489
2490        let mut line = String::new();
2491        while reader.read_line(&mut line)? > 0 {
2492            lines.push(line.to_owned());
2493            line.clear();
2494        }
2495        let content = lines.join("");
2496        let expected: TokenStream = content.parse().unwrap();
2497        println!("{:?}", entity_serde_variant.1);
2498        let generated = generator(
2499            cake_entity,
2500            &entity_serde_variant.1,
2501            &DateTimeCrate::Chrono,
2502            &entity_serde_variant.2,
2503            serde_skip_deserializing_primary_key,
2504            serde_skip_hidden_column,
2505            &TokenStream::new(),
2506            &TokenStream::new(),
2507            &TokenStream::new(),
2508            false,
2509            true,
2510        )
2511        .into_iter()
2512        .fold(TokenStream::new(), |mut acc, tok| {
2513            acc.extend(tok);
2514            acc
2515        });
2516
2517        assert_eq!(expected.to_string(), generated.to_string());
2518        Ok(())
2519    }
2520
2521    #[test]
2522    fn test_gen_with_attributes() -> io::Result<()> {
2523        let cake_entity = setup().get(0).unwrap().clone();
2524
2525        assert_eq!(cake_entity.get_table_name_snake_case(), "cake");
2526
2527        // Compact code blocks
2528        assert_eq!(
2529            comparable_file_string(include_str!(
2530                "../../tests/compact_with_attributes/cake_none.rs"
2531            ))?,
2532            generated_to_string(EntityWriter::gen_compact_code_blocks(
2533                &cake_entity,
2534                &WithSerde::None,
2535                &DateTimeCrate::Chrono,
2536                &None,
2537                false,
2538                false,
2539                &TokenStream::new(),
2540                &TokenStream::new(),
2541                &TokenStream::new(),
2542                false,
2543                true,
2544            ))
2545        );
2546        assert_eq!(
2547            comparable_file_string(include_str!(
2548                "../../tests/compact_with_attributes/cake_one.rs"
2549            ))?,
2550            generated_to_string(EntityWriter::gen_compact_code_blocks(
2551                &cake_entity,
2552                &WithSerde::None,
2553                &DateTimeCrate::Chrono,
2554                &None,
2555                false,
2556                false,
2557                &TokenStream::new(),
2558                &bonus_attributes([r#"serde(rename_all = "camelCase")"#]),
2559                &TokenStream::new(),
2560                false,
2561                true,
2562            ))
2563        );
2564        assert_eq!(
2565            comparable_file_string(include_str!(
2566                "../../tests/compact_with_attributes/cake_multiple.rs"
2567            ))?,
2568            generated_to_string(EntityWriter::gen_compact_code_blocks(
2569                &cake_entity,
2570                &WithSerde::None,
2571                &DateTimeCrate::Chrono,
2572                &None,
2573                false,
2574                false,
2575                &TokenStream::new(),
2576                &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]),
2577                &TokenStream::new(),
2578                false,
2579                true,
2580            ))
2581        );
2582
2583        // Expanded code blocks
2584        assert_eq!(
2585            comparable_file_string(include_str!(
2586                "../../tests/expanded_with_attributes/cake_none.rs"
2587            ))?,
2588            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2589                &cake_entity,
2590                &WithSerde::None,
2591                &DateTimeCrate::Chrono,
2592                &None,
2593                false,
2594                false,
2595                &TokenStream::new(),
2596                &TokenStream::new(),
2597                &TokenStream::new(),
2598                false,
2599                true,
2600            ))
2601        );
2602        assert_eq!(
2603            comparable_file_string(include_str!(
2604                "../../tests/expanded_with_attributes/cake_one.rs"
2605            ))?,
2606            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2607                &cake_entity,
2608                &WithSerde::None,
2609                &DateTimeCrate::Chrono,
2610                &None,
2611                false,
2612                false,
2613                &TokenStream::new(),
2614                &bonus_attributes([r#"serde(rename_all = "camelCase")"#]),
2615                &TokenStream::new(),
2616                false,
2617                true,
2618            ))
2619        );
2620        assert_eq!(
2621            comparable_file_string(include_str!(
2622                "../../tests/expanded_with_attributes/cake_multiple.rs"
2623            ))?,
2624            generated_to_string(EntityWriter::gen_expanded_code_blocks(
2625                &cake_entity,
2626                &WithSerde::None,
2627                &DateTimeCrate::Chrono,
2628                &None,
2629                false,
2630                false,
2631                &TokenStream::new(),
2632                &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]),
2633                &TokenStream::new(),
2634                false,
2635                true,
2636            ))
2637        );
2638
2639        // Frontend code blocks
2640        assert_eq!(
2641            comparable_file_string(include_str!(
2642                "../../tests/frontend_with_attributes/cake_none.rs"
2643            ))?,
2644            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2645                &cake_entity,
2646                &WithSerde::None,
2647                &DateTimeCrate::Chrono,
2648                &None,
2649                false,
2650                false,
2651                &TokenStream::new(),
2652                &TokenStream::new(),
2653                &TokenStream::new(),
2654                false,
2655                true,
2656            ))
2657        );
2658        assert_eq!(
2659            comparable_file_string(include_str!(
2660                "../../tests/frontend_with_attributes/cake_one.rs"
2661            ))?,
2662            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2663                &cake_entity,
2664                &WithSerde::None,
2665                &DateTimeCrate::Chrono,
2666                &None,
2667                false,
2668                false,
2669                &TokenStream::new(),
2670                &bonus_attributes([r#"serde(rename_all = "camelCase")"#]),
2671                &TokenStream::new(),
2672                false,
2673                true,
2674            ))
2675        );
2676        assert_eq!(
2677            comparable_file_string(include_str!(
2678                "../../tests/frontend_with_attributes/cake_multiple.rs"
2679            ))?,
2680            generated_to_string(EntityWriter::gen_frontend_code_blocks(
2681                &cake_entity,
2682                &WithSerde::None,
2683                &DateTimeCrate::Chrono,
2684                &None,
2685                false,
2686                false,
2687                &TokenStream::new(),
2688                &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]),
2689                &TokenStream::new(),
2690                false,
2691                true,
2692            ))
2693        );
2694
2695        Ok(())
2696    }
2697
2698    fn generated_to_string(generated: Vec<TokenStream>) -> String {
2699        generated
2700            .into_iter()
2701            .fold(TokenStream::new(), |mut acc, tok| {
2702                acc.extend(tok);
2703                acc
2704            })
2705            .to_string()
2706    }
2707
2708    fn comparable_file_string(file: &str) -> io::Result<String> {
2709        let mut reader = BufReader::new(file.as_bytes());
2710        let mut lines: Vec<String> = Vec::new();
2711
2712        reader.read_until(b'\n', &mut Vec::new())?;
2713
2714        let mut line = String::new();
2715        while reader.read_line(&mut line)? > 0 {
2716            lines.push(line.to_owned());
2717            line.clear();
2718        }
2719        let content = lines.join("");
2720        let expected: TokenStream = content.parse().unwrap();
2721
2722        Ok(expected.to_string())
2723    }
2724
2725    #[test]
2726    fn test_gen_postgres() -> io::Result<()> {
2727        let entities = vec![
2728            // This tests that the JsonBinary column type is annotated
2729            // correctly in compact entity form. More information can be found
2730            // in this issue:
2731            //
2732            // https://github.com/SeaQL/sea-orm/issues/1344
2733            Entity {
2734                table_name: "task".to_owned(),
2735                columns: vec![
2736                    Column {
2737                        name: "id".to_owned(),
2738                        col_type: ColumnType::Integer,
2739                        auto_increment: true,
2740                        not_null: true,
2741                        unique: false,
2742                        unique_key: None,
2743                    },
2744                    Column {
2745                        name: "payload".to_owned(),
2746                        col_type: ColumnType::Json,
2747                        auto_increment: false,
2748                        not_null: true,
2749                        unique: false,
2750                        unique_key: None,
2751                    },
2752                    Column {
2753                        name: "payload_binary".to_owned(),
2754                        col_type: ColumnType::JsonBinary,
2755                        auto_increment: false,
2756                        not_null: true,
2757                        unique: false,
2758                        unique_key: None,
2759                    },
2760                ],
2761                relations: vec![],
2762                conjunct_relations: vec![],
2763                primary_keys: vec![PrimaryKey {
2764                    name: "id".to_owned(),
2765                }],
2766            },
2767        ];
2768        const ENTITY_FILES: [&str; 1] = [include_str!("../../tests/postgres/binary_json.rs")];
2769
2770        const ENTITY_FILES_EXPANDED: [&str; 1] =
2771            [include_str!("../../tests/postgres/binary_json_expanded.rs")];
2772
2773        assert_eq!(entities.len(), ENTITY_FILES.len());
2774
2775        for (i, entity) in entities.iter().enumerate() {
2776            assert_eq!(
2777                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
2778                EntityWriter::gen_compact_code_blocks(
2779                    entity,
2780                    &crate::WithSerde::None,
2781                    &crate::DateTimeCrate::Chrono,
2782                    &None,
2783                    false,
2784                    false,
2785                    &TokenStream::new(),
2786                    &TokenStream::new(),
2787                    &TokenStream::new(),
2788                    false,
2789                    true,
2790                )
2791                .into_iter()
2792                .skip(1)
2793                .fold(TokenStream::new(), |mut acc, tok| {
2794                    acc.extend(tok);
2795                    acc
2796                })
2797                .to_string()
2798            );
2799            assert_eq!(
2800                parse_from_file(ENTITY_FILES_EXPANDED[i].as_bytes())?.to_string(),
2801                EntityWriter::gen_expanded_code_blocks(
2802                    entity,
2803                    &crate::WithSerde::None,
2804                    &crate::DateTimeCrate::Chrono,
2805                    &Some("schema_name".to_owned()),
2806                    false,
2807                    false,
2808                    &TokenStream::new(),
2809                    &TokenStream::new(),
2810                    &TokenStream::new(),
2811                    false,
2812                    true,
2813                )
2814                .into_iter()
2815                .skip(1)
2816                .fold(TokenStream::new(), |mut acc, tok| {
2817                    acc.extend(tok);
2818                    acc
2819                })
2820                .to_string()
2821            );
2822        }
2823
2824        Ok(())
2825    }
2826
2827    #[test]
2828    fn test_gen_import_active_enum() -> io::Result<()> {
2829        let entities = vec![
2830            Entity {
2831                table_name: "tea_pairing".to_owned(),
2832                columns: vec![
2833                    Column {
2834                        name: "id".to_owned(),
2835                        col_type: ColumnType::Integer,
2836                        auto_increment: true,
2837                        not_null: true,
2838                        unique: false,
2839                        unique_key: None,
2840                    },
2841                    Column {
2842                        name: "first_tea".to_owned(),
2843                        col_type: ColumnType::Enum {
2844                            name: SeaRc::new(Alias::new("tea_enum")),
2845                            variants: vec![
2846                                SeaRc::new(Alias::new("everyday_tea")),
2847                                SeaRc::new(Alias::new("breakfast_tea")),
2848                            ],
2849                        },
2850                        auto_increment: false,
2851                        not_null: true,
2852                        unique: false,
2853                        unique_key: None,
2854                    },
2855                    Column {
2856                        name: "second_tea".to_owned(),
2857                        col_type: ColumnType::Enum {
2858                            name: SeaRc::new(Alias::new("tea_enum")),
2859                            variants: vec![
2860                                SeaRc::new(Alias::new("everyday_tea")),
2861                                SeaRc::new(Alias::new("breakfast_tea")),
2862                            ],
2863                        },
2864                        auto_increment: false,
2865                        not_null: true,
2866                        unique: false,
2867                        unique_key: None,
2868                    },
2869                ],
2870                relations: vec![],
2871                conjunct_relations: vec![],
2872                primary_keys: vec![PrimaryKey {
2873                    name: "id".to_owned(),
2874                }],
2875            },
2876            Entity {
2877                table_name: "tea_pairing_with_size".to_owned(),
2878                columns: vec![
2879                    Column {
2880                        name: "id".to_owned(),
2881                        col_type: ColumnType::Integer,
2882                        auto_increment: true,
2883                        not_null: true,
2884                        unique: false,
2885                        unique_key: None,
2886                    },
2887                    Column {
2888                        name: "first_tea".to_owned(),
2889                        col_type: ColumnType::Enum {
2890                            name: SeaRc::new(Alias::new("tea_enum")),
2891                            variants: vec![
2892                                SeaRc::new(Alias::new("everyday_tea")),
2893                                SeaRc::new(Alias::new("breakfast_tea")),
2894                            ],
2895                        },
2896                        auto_increment: false,
2897                        not_null: true,
2898                        unique: false,
2899                        unique_key: None,
2900                    },
2901                    Column {
2902                        name: "second_tea".to_owned(),
2903                        col_type: ColumnType::Enum {
2904                            name: SeaRc::new(Alias::new("tea_enum")),
2905                            variants: vec![
2906                                SeaRc::new(Alias::new("everyday_tea")),
2907                                SeaRc::new(Alias::new("breakfast_tea")),
2908                            ],
2909                        },
2910                        auto_increment: false,
2911                        not_null: true,
2912                        unique: false,
2913                        unique_key: None,
2914                    },
2915                    Column {
2916                        name: "size".to_owned(),
2917                        col_type: ColumnType::Enum {
2918                            name: SeaRc::new(Alias::new("tea_size")),
2919                            variants: vec![
2920                                SeaRc::new(Alias::new("small")),
2921                                SeaRc::new(Alias::new("medium")),
2922                                SeaRc::new(Alias::new("huge")),
2923                            ],
2924                        },
2925                        auto_increment: false,
2926                        not_null: true,
2927                        unique: false,
2928                        unique_key: None,
2929                    },
2930                ],
2931                relations: vec![],
2932                conjunct_relations: vec![],
2933                primary_keys: vec![PrimaryKey {
2934                    name: "id".to_owned(),
2935                }],
2936            },
2937        ];
2938
2939        assert_eq!(
2940            quote!(
2941                use super::sea_orm_active_enums::TeaEnum;
2942            )
2943            .to_string(),
2944            EntityWriter::gen_import_active_enum(&entities[0]).to_string()
2945        );
2946
2947        assert_eq!(
2948            quote!(
2949                use super::sea_orm_active_enums::TeaEnum;
2950                use super::sea_orm_active_enums::TeaSize;
2951            )
2952            .to_string(),
2953            EntityWriter::gen_import_active_enum(&entities[1]).to_string()
2954        );
2955
2956        Ok(())
2957    }
2958
2959    #[test]
2960    fn test_gen_dense_code_blocks() -> io::Result<()> {
2961        let entities = setup();
2962        const ENTITY_FILES: [&str; 13] = [
2963            include_str!("../../tests/dense/cake.rs"),
2964            include_str!("../../tests/dense/cake_filling.rs"),
2965            include_str!("../../tests/dense/cake_filling_price.rs"),
2966            include_str!("../../tests/dense/filling.rs"),
2967            include_str!("../../tests/dense/fruit.rs"),
2968            include_str!("../../tests/dense/vendor.rs"),
2969            include_str!("../../tests/dense/rust_keyword.rs"),
2970            include_str!("../../tests/dense/cake_with_float.rs"),
2971            include_str!("../../tests/dense/cake_with_double.rs"),
2972            include_str!("../../tests/dense/collection.rs"),
2973            include_str!("../../tests/dense/collection_float.rs"),
2974            include_str!("../../tests/dense/parent.rs"),
2975            include_str!("../../tests/dense/child.rs"),
2976        ];
2977
2978        assert_eq!(entities.len(), ENTITY_FILES.len());
2979
2980        for (i, entity) in entities.iter().enumerate() {
2981            assert_eq!(
2982                parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(),
2983                EntityWriter::gen_dense_code_blocks(
2984                    entity,
2985                    &crate::WithSerde::None,
2986                    &crate::DateTimeCrate::Chrono,
2987                    &None,
2988                    false,
2989                    false,
2990                    &TokenStream::new(),
2991                    &TokenStream::new(),
2992                    &TokenStream::new(),
2993                    false,
2994                    true,
2995                )
2996                .into_iter()
2997                .skip(1)
2998                .fold(TokenStream::new(), |mut acc, tok| {
2999                    acc.extend(tok);
3000                    acc
3001                })
3002                .to_string()
3003            );
3004        }
3005
3006        Ok(())
3007    }
3008}