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