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