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
110pub(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
125pub(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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}