From 3300336b1ab1790f8e5a886edc2d7b1fc3935318 Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Fri, 19 May 2023 17:14:46 +0300 Subject: [PATCH] Expand SeaORM entity generator with Seaography related data (#1599) * Add DeriveRelatedEntity macro * Add generation for related enum and seaography * Add seaography cli param * update codegen tests * Fix DeriveRelatedEntity macro doc and includes * Fix all RelatedEntity variants for RelationBuilder * Add tests for code * Cargo format * Fix clippy code * Fix format * Fix unit tests * Fix unit tests * Provide default for seaography::RelationBuilder * Update changelog * Update tests * Modify code to match feedback * Bring old Related Impl trait generation * Modify DeriveRelatedEntity to gen impl seaography::RelationBuilder * Generate RelatedEntity enum when seaography flag is enabled * Update documentation * Update Changelog * Fix format errors * Fix code generation * relations with suffix are definition based * Rev => Reverse easier to read * snake_case to cameCase for name generation * Fix unit tests * Update lib.rs * derive `seaography::RelationBuilder` only when `seaography` feature is enabled * Try constructing async-graphql root for "related entity" and "entity" without relation * Update demo * CHANGELOG * Update Cargo.toml Co-authored-by: Chris Tsang * Revert "Update Cargo.toml" This reverts commit 6b1669836a4fb5040bfb08999f0cf640c74dc64d. --------- Co-authored-by: Billy Chan Co-authored-by: Chris Tsang --- CHANGELOG.md | 29 +++- Cargo.toml | 1 + issues/1599/Cargo.toml | 2 + issues/1599/entity/Cargo.toml | 17 ++ issues/1599/entity/src/cake.rs | 60 +++++++ issues/1599/entity/src/cake_filling.rs | 70 ++++++++ issues/1599/entity/src/filling.rs | 80 +++++++++ issues/1599/entity/src/fruit.rs | 29 ++++ issues/1599/entity/src/lib.rs | 4 + issues/1599/graphql/Cargo.toml | 20 +++ issues/1599/graphql/src/main.rs | 64 +++++++ issues/1599/graphql/src/query_root.rs | 34 ++++ sea-orm-cli/src/cli.rs | 8 + sea-orm-cli/src/commands/generate.rs | 2 + sea-orm-codegen/src/entity/base_entity.rs | 78 +++++++++ sea-orm-codegen/src/entity/transformer.rs | 1 + sea-orm-codegen/src/entity/writer.rs | 157 ++++++++++++++++++ sea-orm-codegen/tests/with_seaography/cake.rs | 50 ++++++ .../tests/with_seaography/cake_expanded.rs | 94 +++++++++++ sea-orm-macros/Cargo.toml | 1 + sea-orm-macros/src/derives/attributes.rs | 26 +++ sea-orm-macros/src/derives/mod.rs | 2 + sea-orm-macros/src/derives/related_entity.rs | 125 ++++++++++++++ sea-orm-macros/src/lib.rs | 40 +++++ src/entity/prelude.rs | 2 +- src/lib.rs | 4 +- src/tests_cfg/cake_seaography.rs | 64 +++++++ 27 files changed, 1060 insertions(+), 4 deletions(-) create mode 100644 issues/1599/Cargo.toml create mode 100644 issues/1599/entity/Cargo.toml create mode 100644 issues/1599/entity/src/cake.rs create mode 100644 issues/1599/entity/src/cake_filling.rs create mode 100644 issues/1599/entity/src/filling.rs create mode 100644 issues/1599/entity/src/fruit.rs create mode 100644 issues/1599/entity/src/lib.rs create mode 100644 issues/1599/graphql/Cargo.toml create mode 100644 issues/1599/graphql/src/main.rs create mode 100644 issues/1599/graphql/src/query_root.rs create mode 100644 sea-orm-codegen/tests/with_seaography/cake.rs create mode 100644 sea-orm-codegen/tests/with_seaography/cake_expanded.rs create mode 100644 sea-orm-macros/src/derives/related_entity.rs create mode 100644 src/tests_cfg/cake_seaography.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ccfbe05..94875b53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -185,6 +185,33 @@ assert_eq!( ``` * [sea-orm-cli] Added support for generating migration of space separated name, for example executing `sea-orm-cli migrate generate "create accounts table"` command will create `m20230503_000000_create_accounts_table.rs` for you https://github.com/SeaQL/sea-orm/pull/1570 +* Add `seaography` flag to `sea-orm`, `sea-orm-orm-macros` and `sea-orm-cli` https://github.com/SeaQL/sea-orm/pull/1599 +* Add generation of `seaography` related information to `sea-orm-codegen` https://github.com/SeaQL/sea-orm/pull/1599 + + The following information is added in entities files by `sea-orm-cli` when flag `seaography` is `true` +```rust +/// ... Entity File ... + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::address::Entity")] + Address, + #[sea_orm(entity = "super::payment::Entity")] + Payment, + #[sea_orm(entity = "super::rental::Entity")] + Rental, + #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] + SelfRef, + #[sea_orm(entity = "super::store::Entity")] + Store, + #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] + SelfRefRev, +} +``` +* Add `DeriveEntityRelated` macro https://github.com/SeaQL/sea-orm/pull/1599 + + The DeriveRelatedEntity derive macro will implement `seaography::RelationBuilder` for `RelatedEntity` enumeration when the `seaography` feature is enabled + ### Enhancements * Added `Migration::name()` and `Migration::status()` getters for the name and status of `sea_orm_migration::Migration` https://github.com/SeaQL/sea-orm/pull/1519 @@ -457,7 +484,7 @@ impl ColumnTrait for Column { ### Breaking Changes * [sea-orm-cli] Enable --universal-time by default https://github.com/SeaQL/sea-orm/pull/1420 -* Added `RecordNotInserted` and `RecordNotUpdated` to `DbErr` +* Added `RecordNotInserted` and `RecordNotUpdated` to `DbErr` * Added `ConnectionTrait::execute_unprepared` method https://github.com/SeaQL/sea-orm/pull/1327 * As part of https://github.com/SeaQL/sea-orm/pull/1311, the required method of `TryGetable` changed: ```rust diff --git a/Cargo.toml b/Cargo.toml index ade001a0..f5aaecd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,3 +122,4 @@ runtime-tokio-rustls = [ "runtime-tokio", ] tests-cfg = ["serde/derive"] +seaography = ["sea-orm-macros/seaography"] diff --git a/issues/1599/Cargo.toml b/issues/1599/Cargo.toml new file mode 100644 index 00000000..de74d52a --- /dev/null +++ b/issues/1599/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["entity", "graphql"] diff --git a/issues/1599/entity/Cargo.toml b/issues/1599/entity/Cargo.toml new file mode 100644 index 00000000..f34b35bf --- /dev/null +++ b/issues/1599/entity/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "entity" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "entity" +path = "src/lib.rs" + +[dependencies] +sea-orm = { path = "../../../" } +seaography = { path = "../../../../seaography", optional = true } +async-graphql = { version = "5", optional = true } + +[features] +seaography = ["dep:seaography", "async-graphql", "sea-orm/seaography"] diff --git a/issues/1599/entity/src/cake.rs b/issues/1599/entity/src/cake.rs new file mode 100644 index 00000000..87025c9f --- /dev/null +++ b/issues/1599/entity/src/cake.rs @@ -0,0 +1,60 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "cake")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_name = "name", enum_name = "Name")] + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, + #[sea_orm( + has_many = "super::fruit::Entity", + on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# + )] + TropicalFruit, + #[sea_orm( + has_many = "super::fruit::Entity", + condition_type = "any", + on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# + )] + OrTropicalFruit, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::fruit::Entity")] + Fruit, + #[sea_orm(entity = "super::filling::Entity")] + Filling, + #[sea_orm(entity = "super::fruit::Entity", def = "Relation::TropicalFruit.def()")] + TropicalFruit, + #[sea_orm( + entity = "super::fruit::Entity", + def = "Relation::OrTropicalFruit.def()" + )] + OrTropicalFruit, +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/issues/1599/entity/src/cake_filling.rs b/issues/1599/entity/src/cake_filling.rs new file mode 100644 index 00000000..1ce203f6 --- /dev/null +++ b/issues/1599/entity/src/cake_filling.rs @@ -0,0 +1,70 @@ +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cake_filling" + } +} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub cake_id: i32, + pub filling_id: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + CakeId, + FillingId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + CakeId, + FillingId, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = (i32, i32); + + fn auto_increment() -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Filling, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::CakeId => ColumnType::Integer.def(), + Self::FillingId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Filling => Entity::belongs_to(super::filling::Entity) + .from(Column::FillingId) + .to(super::filling::Column::Id) + .into(), + } + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/issues/1599/entity/src/filling.rs b/issues/1599/entity/src/filling.rs new file mode 100644 index 00000000..21a90301 --- /dev/null +++ b/issues/1599/entity/src/filling.rs @@ -0,0 +1,80 @@ +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +#[sea_orm(table_name = "filling")] +pub struct Entity; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub vendor_id: Option, + #[sea_orm(ignore)] + pub ignored_attr: i32, +} + +// If your column names are not in snake-case, derive `DeriveCustomColumn` here. +#[derive(Copy, Clone, Debug, EnumIter, DeriveCustomColumn)] +pub enum Column { + Id, + Name, + VendorId, +} + +// Then, customize each column names here. +impl IdenStatic for Column { + fn as_str(&self) -> &str { + match self { + // Override column names + Self::Id => "id", + // Leave all other columns using default snake-case values + _ => self.default_as_str(), + } + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(None).def(), + Self::VendorId => ColumnType::Integer.def().nullable(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Cake.def() + } + + fn via() -> Option { + Some(super::cake_filling::Relation::Filling.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/issues/1599/entity/src/fruit.rs b/issues/1599/entity/src/fruit.rs new file mode 100644 index 00000000..5e396f08 --- /dev/null +++ b/issues/1599/entity/src/fruit.rs @@ -0,0 +1,29 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "fruit")] +pub struct Model { + #[sea_orm(primary_key)] + #[cfg_attr(feature = "with-json", serde(skip_deserializing))] + pub id: i32, + pub name: String, + pub cake_id: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::cake::Entity", + from = "Column::CakeId", + to = "super::cake::Column::Id" + )] + Cake, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/issues/1599/entity/src/lib.rs b/issues/1599/entity/src/lib.rs new file mode 100644 index 00000000..a65bb013 --- /dev/null +++ b/issues/1599/entity/src/lib.rs @@ -0,0 +1,4 @@ +pub mod cake; +pub mod cake_filling; +pub mod filling; +pub mod fruit; diff --git a/issues/1599/graphql/Cargo.toml b/issues/1599/graphql/Cargo.toml new file mode 100644 index 00000000..465d0924 --- /dev/null +++ b/issues/1599/graphql/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "graphql" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +poem = { version = "1.3.55" } +async-graphql-poem = { version = "5.0.6" } +async-graphql = { version = "5.0.6", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] } +async-trait = { version = "0.1.64" } +dotenv = "0.15.0" +tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] } +tracing = { version = "0.1.37" } +tracing-subscriber = { version = "0.3.16" } +lazy_static = { version = "1.4.0" } + +sea-orm = { path = "../../../" } +entity = { path = "../entity", features = ["seaography"] } +seaography = { path = "../../../../seaography" } diff --git a/issues/1599/graphql/src/main.rs b/issues/1599/graphql/src/main.rs new file mode 100644 index 00000000..897ca83c --- /dev/null +++ b/issues/1599/graphql/src/main.rs @@ -0,0 +1,64 @@ +use async_graphql::{ + dataloader::DataLoader, + http::{playground_source, GraphQLPlaygroundConfig}, +}; +use async_graphql_poem::GraphQL; +use dotenv::dotenv; +use lazy_static::lazy_static; +use poem::{get, handler, listener::TcpListener, web::Html, IntoResponse, Route, Server}; +use sea_orm::{prelude::*, Database}; +use std::env; + +pub mod query_root; + +pub struct OrmDataloader { + pub db: DatabaseConnection, +} + +lazy_static! { + static ref URL: String = env::var("URL").unwrap_or("0.0.0.0:8000".into()); + static ref ENDPOINT: String = env::var("ENDPOINT").unwrap_or("/".into()); + static ref DATABASE_URL: String = + env::var("DATABASE_URL").expect("DATABASE_URL environment variable not set"); + static ref DEPTH_LIMIT: Option = env::var("DEPTH_LIMIT").map_or(None, |data| Some( + data.parse().expect("DEPTH_LIMIT is not a number") + )); + static ref COMPLEXITY_LIMIT: Option = env::var("COMPLEXITY_LIMIT") + .map_or(None, |data| { + Some(data.parse().expect("COMPLEXITY_LIMIT is not a number")) + }); +} + +#[handler] +async fn graphql_playground() -> impl IntoResponse { + Html(playground_source(GraphQLPlaygroundConfig::new(&ENDPOINT))) +} + +#[tokio::main] +async fn main() { + dotenv().ok(); + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_test_writer() + .init(); + let database = Database::connect(&*DATABASE_URL) + .await + .expect("Fail to initialize database connection"); + let orm_dataloader: DataLoader = DataLoader::new( + OrmDataloader { + db: database.clone(), + }, + tokio::spawn, + ); + let schema = + query_root::schema(database, orm_dataloader, *DEPTH_LIMIT, *COMPLEXITY_LIMIT).unwrap(); + let app = Route::new().at( + &*ENDPOINT, + get(graphql_playground).post(GraphQL::new(schema)), + ); + println!("Visit GraphQL Playground at http://{}", *URL); + Server::new(TcpListener::bind(&*URL)) + .run(app) + .await + .expect("Fail to start web server"); +} diff --git a/issues/1599/graphql/src/query_root.rs b/issues/1599/graphql/src/query_root.rs new file mode 100644 index 00000000..2c685522 --- /dev/null +++ b/issues/1599/graphql/src/query_root.rs @@ -0,0 +1,34 @@ +use crate::OrmDataloader; +use async_graphql::{dataloader::DataLoader, dynamic::*}; +use entity::*; +use sea_orm::DatabaseConnection; +use seaography::{Builder, BuilderContext}; + +lazy_static::lazy_static! { static ref CONTEXT : BuilderContext = BuilderContext :: default () ; } + +pub fn schema( + database: DatabaseConnection, + orm_dataloader: DataLoader, + depth: Option, + complexity: Option, +) -> Result { + let mut builder = Builder::new(&CONTEXT); + + // Register entity including relations + seaography::register_entities!(builder, [cake]); + // Register entity only, no relations + seaography::register_entities_without_relation!(builder, [cake_filling, filling, fruit]); + + let schema = builder.schema_builder(); + let schema = if let Some(depth) = depth { + schema.limit_depth(depth) + } else { + schema + }; + let schema = if let Some(complexity) = complexity { + schema.limit_complexity(complexity) + } else { + schema + }; + schema.data(database).data(orm_dataloader).finish() +} diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index c215b8a1..d37c5ca0 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -321,6 +321,14 @@ pub enum GenerateSubcommands { help = r#"Add extra attributes to generated model struct, no need for `#[]` (comma separated), e.g. `--model-extra-attributes 'serde(rename_all = "camelCase")','ts(export)'`"# )] model_extra_attributes: Vec, + + #[clap( + action, + long, + default_value = "false", + long_help = "Generate helper Enumerations that are used by Seaography." + )] + seaography: bool, }, } diff --git a/sea-orm-cli/src/commands/generate.rs b/sea-orm-cli/src/commands/generate.rs index 97ec8503..af6d9f7b 100644 --- a/sea-orm-cli/src/commands/generate.rs +++ b/sea-orm-cli/src/commands/generate.rs @@ -31,6 +31,7 @@ pub async fn run_generate_command( lib, model_extra_derives, model_extra_attributes, + seaography, } => { if verbose { let _ = tracing_subscriber::fmt() @@ -172,6 +173,7 @@ pub async fn run_generate_command( serde_skip_hidden_column, model_extra_derives, model_extra_attributes, + seaography, ); let output = EntityTransformer::transform(table_stmts)?.generate(&writer_context); diff --git a/sea-orm-codegen/src/entity/base_entity.rs b/sea-orm-codegen/src/entity/base_entity.rs index 1f184597..c7a89b96 100644 --- a/sea-orm-codegen/src/entity/base_entity.rs +++ b/sea-orm-codegen/src/entity/base_entity.rs @@ -92,6 +92,26 @@ impl Entity { .collect() } + /// Used to generate the names for the `enum RelatedEntity` that is useful to the Seaography project + pub fn get_related_entity_enum_name(&self) -> Vec { + // 1st step get conjunct relations data + let conjunct_related_names = self.get_conjunct_relations_to_upper_camel_case(); + + // 2nd step get reverse self relations data + let self_relations_reverse = self + .relations + .iter() + .filter(|rel| rel.self_referencing) + .map(|rel| format_ident!("{}Reverse", rel.get_enum_name())); + + // 3rd step get normal relations data + self.get_relation_enum_name() + .into_iter() + .chain(self_relations_reverse) + .chain(conjunct_related_names.into_iter()) + .collect() + } + pub fn get_relation_defs(&self) -> Vec { self.relations.iter().map(|rel| rel.get_def()).collect() } @@ -100,6 +120,64 @@ impl Entity { self.relations.iter().map(|rel| rel.get_attrs()).collect() } + /// Used to generate the attributes for the `enum RelatedEntity` that is useful to the Seaography project + pub fn get_related_entity_attrs(&self) -> Vec { + // 1st step get conjunct relations data + let conjunct_related_attrs = self.conjunct_relations.iter().map(|conj| { + let entity = format!("super::{}::Entity", conj.get_to_snake_case()); + + quote! { + #[sea_orm( + entity = #entity + )] + } + }); + + // helper function that generates attributes for `Relation` data + let produce_relation_attrs = |rel: &Relation, reverse: bool| { + let entity = match rel.get_module_name() { + Some(module_name) => format!("super::{}::Entity", module_name), + None => String::from("Entity"), + }; + + if rel.self_referencing || !rel.impl_related || rel.num_suffix > 0 { + let def = if reverse { + format!("Relation::{}.def().rev()", rel.get_enum_name()) + } else { + format!("Relation::{}.def()", rel.get_enum_name()) + }; + + quote! { + #[sea_orm( + entity = #entity, + def = #def + )] + } + } else { + quote! { + #[sea_orm( + entity = #entity + )] + } + } + }; + + // 2nd step get reverse self relations data + let self_relations_reverse_attrs = self + .relations + .iter() + .filter(|rel| rel.self_referencing) + .map(|rel| produce_relation_attrs(rel, true)); + + // 3rd step get normal relations data + self.relations + .iter() + .map(|rel| produce_relation_attrs(rel, false)) + .chain(self_relations_reverse_attrs) + .chain(conjunct_related_attrs) + .collect() + } + pub fn get_primary_key_auto_increment(&self) -> Ident { let auto_increment = self.columns.iter().any(|col| col.auto_increment); format_ident!("{}", auto_increment) diff --git a/sea-orm-codegen/src/entity/transformer.rs b/sea-orm-codegen/src/entity/transformer.rs index 06a23b52..421a83bf 100644 --- a/sea-orm-codegen/src/entity/transformer.rs +++ b/sea-orm-codegen/src/entity/transformer.rs @@ -385,6 +385,7 @@ mod tests { false, &Default::default(), &Default::default(), + false, ) .into_iter() .skip(1) diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 6338f1a1..36da9891 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -47,6 +47,7 @@ pub struct EntityWriterContext { pub(crate) serde_skip_deserializing_primary_key: bool, pub(crate) model_extra_derives: TokenStream, pub(crate) model_extra_attributes: TokenStream, + pub(crate) seaography: bool, } impl WithSerde { @@ -142,6 +143,7 @@ impl EntityWriterContext { serde_skip_hidden_column: bool, model_extra_derives: Vec, model_extra_attributes: Vec, + seaography: bool, ) -> Self { Self { expanded_format, @@ -154,6 +156,7 @@ impl EntityWriterContext { serde_skip_hidden_column, model_extra_derives: bonus_derive(model_extra_derives), model_extra_attributes: bonus_attributes(model_extra_attributes), + seaography, } } } @@ -209,6 +212,7 @@ impl EntityWriter { serde_skip_hidden_column, &context.model_extra_derives, &context.model_extra_attributes, + context.seaography, ) } else { Self::gen_compact_code_blocks( @@ -220,6 +224,7 @@ impl EntityWriter { serde_skip_hidden_column, &context.model_extra_derives, &context.model_extra_attributes, + context.seaography, ) }; Self::write(&mut lines, code_blocks); @@ -323,6 +328,7 @@ impl EntityWriter { serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, + seaography: bool, ) -> Vec { let mut imports = Self::gen_import(with_serde); imports.extend(Self::gen_import_active_enum(entity)); @@ -349,6 +355,9 @@ impl EntityWriter { code_blocks.extend(Self::gen_impl_related(entity)); code_blocks.extend(Self::gen_impl_conjunct_related(entity)); code_blocks.extend([Self::gen_impl_active_model_behavior()]); + if seaography { + code_blocks.extend([Self::gen_related_entity(entity)]); + } code_blocks } @@ -362,6 +371,7 @@ impl EntityWriter { serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, + seaography: bool, ) -> Vec { let mut imports = Self::gen_import(with_serde); imports.extend(Self::gen_import_active_enum(entity)); @@ -382,6 +392,9 @@ impl EntityWriter { code_blocks.extend(Self::gen_impl_related(entity)); code_blocks.extend(Self::gen_impl_conjunct_related(entity)); code_blocks.extend([Self::gen_impl_active_model_behavior()]); + if seaography { + code_blocks.extend([Self::gen_related_entity(entity)]); + } code_blocks } @@ -608,6 +621,22 @@ impl EntityWriter { .collect() } + /// Used to generate `enum RelatedEntity` that is useful to the Seaography project + pub fn gen_related_entity(entity: &Entity) -> TokenStream { + let related_enum_name = entity.get_related_entity_enum_name(); + let related_attrs = entity.get_related_entity_attrs(); + + quote! { + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] + pub enum RelatedEntity { + #( + #related_attrs + #related_enum_name + ),* + } + } + } + pub fn gen_impl_conjunct_related(entity: &Entity) -> Vec { let table_name_camel_case = entity.get_table_name_camel_case_ident(); let via_snake_case = entity.get_conjunct_relations_via_snake_case(); @@ -1371,6 +1400,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false ) .into_iter() .skip(1) @@ -1391,6 +1421,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) @@ -1411,6 +1442,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) @@ -1467,6 +1499,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) @@ -1487,6 +1520,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) @@ -1507,6 +1541,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) @@ -1539,6 +1574,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1554,6 +1590,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1569,6 +1606,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1582,6 +1620,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); @@ -1597,6 +1636,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1612,6 +1652,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1627,6 +1668,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1640,6 +1682,104 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, + )) + ); + + Ok(()) + } + + #[test] + fn test_gen_with_seaography() -> io::Result<()> { + let cake_entity = Entity { + table_name: "cake".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::Integer, + auto_increment: true, + not_null: true, + unique: false, + }, + Column { + name: "name".to_owned(), + col_type: ColumnType::Text, + auto_increment: false, + not_null: false, + unique: false, + }, + Column { + name: "base_id".to_owned(), + col_type: ColumnType::Integer, + auto_increment: false, + not_null: false, + unique: false, + }, + ], + relations: vec![ + Relation { + ref_table: "fruit".to_owned(), + columns: vec![], + ref_columns: vec![], + rel_type: RelationType::HasMany, + on_delete: None, + on_update: None, + self_referencing: false, + num_suffix: 0, + impl_related: true, + }, + Relation { + ref_table: "cake".to_owned(), + columns: vec![], + ref_columns: vec![], + rel_type: RelationType::HasOne, + on_delete: None, + on_update: None, + self_referencing: true, + num_suffix: 0, + impl_related: true, + }, + ], + conjunct_relations: vec![ConjunctRelation { + via: "cake_filling".to_owned(), + to: "filling".to_owned(), + }], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + }; + + assert_eq!(cake_entity.get_table_name_snake_case(), "cake"); + + // Compact code blocks + assert_eq!( + comparable_file_string(include_str!("../../tests/with_seaography/cake.rs"))?, + generated_to_string(EntityWriter::gen_compact_code_blocks( + &cake_entity, + &WithSerde::None, + &DateTimeCrate::Chrono, + &None, + false, + false, + &TokenStream::new(), + &TokenStream::new(), + true, + )) + ); + + // Expanded code blocks + assert_eq!( + comparable_file_string(include_str!("../../tests/with_seaography/cake_expanded.rs"))?, + generated_to_string(EntityWriter::gen_expanded_code_blocks( + &cake_entity, + &WithSerde::None, + &DateTimeCrate::Chrono, + &None, + false, + false, + &TokenStream::new(), + &TokenStream::new(), + true, )) ); @@ -1666,6 +1806,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1679,6 +1820,7 @@ mod tests { false, &bonus_derive(["ts_rs::TS"]), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1694,6 +1836,7 @@ mod tests { false, &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]), &TokenStream::new(), + false, )) ); @@ -1711,6 +1854,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1726,6 +1870,7 @@ mod tests { false, &bonus_derive(["ts_rs::TS"]), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1741,6 +1886,7 @@ mod tests { false, &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]), &TokenStream::new(), + false, )) ); @@ -1785,6 +1931,7 @@ mod tests { bool, &TokenStream, &TokenStream, + bool, ) -> Vec, >, ) -> io::Result<()> { @@ -1815,6 +1962,7 @@ mod tests { serde_skip_hidden_column, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .fold(TokenStream::new(), |mut acc, tok| { @@ -1846,6 +1994,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1861,6 +2010,7 @@ mod tests { false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#]), + false, )) ); assert_eq!( @@ -1876,6 +2026,7 @@ mod tests { false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]), + false, )) ); @@ -1893,6 +2044,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, )) ); assert_eq!( @@ -1908,6 +2060,7 @@ mod tests { false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#]), + false, )) ); assert_eq!( @@ -1923,6 +2076,7 @@ mod tests { false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]), + false, )) ); @@ -2015,6 +2169,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) @@ -2035,6 +2190,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) @@ -2055,6 +2211,7 @@ mod tests { false, &TokenStream::new(), &TokenStream::new(), + false, ) .into_iter() .skip(1) diff --git a/sea-orm-codegen/tests/with_seaography/cake.rs b/sea-orm-codegen/tests/with_seaography/cake.rs new file mode 100644 index 00000000..41a3068b --- /dev/null +++ b/sea-orm-codegen/tests/with_seaography/cake.rs @@ -0,0 +1,50 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude:: * ; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "cake")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_type = "Text", nullable)] + pub name: Option , + pub base_id: Option , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, + #[sea_orm(has_one = "Entity")] + SelfRef , +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::fruit::Entity")] + Fruit, + #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] + SelfRef, + #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] + SelfRefReverse, + #[sea_orm(entity = "super::filling::Entity")] + Filling +} diff --git a/sea-orm-codegen/tests/with_seaography/cake_expanded.rs b/sea-orm-codegen/tests/with_seaography/cake_expanded.rs new file mode 100644 index 00000000..b4110b37 --- /dev/null +++ b/sea-orm-codegen/tests/with_seaography/cake_expanded.rs @@ -0,0 +1,94 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude:: * ; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cake" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] +pub struct Model { + pub id: i32, + pub name: Option , + pub base_id: Option , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + BaseId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, + SelfRef , +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::Text.def().null(), + Self::BaseId => ColumnType::Integer.def().null(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), + Self::SelfRef => Entity::has_one(Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::fruit::Entity")] + Fruit, + #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] + SelfRef, + #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] + SelfRefReverse, + #[sea_orm(entity = "super::filling::Entity")] + Filling +} diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index e11ea2ba..ee4c65b1 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -34,3 +34,4 @@ default = ["derive"] postgres-array = [] derive = ["bae"] strum = [] +seaography = [] diff --git a/sea-orm-macros/src/derives/attributes.rs b/sea-orm-macros/src/derives/attributes.rs index f9e3bc42..0b51945b 100644 --- a/sea-orm-macros/src/derives/attributes.rs +++ b/sea-orm-macros/src/derives/attributes.rs @@ -33,3 +33,29 @@ pub mod field_attr { pub condition_type: Option, } } + +pub mod related_attr { + use bae::FromAttributes; + + /// Operations for RelatedEntity enumeration + #[derive(Default, FromAttributes)] + pub struct SeaOrm { + /// + /// Allows to modify target entity + /// + /// Required on enumeration variants + /// + /// If used on enumeration attributes + /// it allows to specify different + /// Entity ident + pub entity: Option, + /// + /// Allows to specify RelationDef + /// + /// Optional + /// + /// If not supplied the generated code + /// will utilize `impl Related` trait + pub def: Option, + } +} diff --git a/sea-orm-macros/src/derives/mod.rs b/sea-orm-macros/src/derives/mod.rs index 5ec025da..29f69b05 100644 --- a/sea-orm-macros/src/derives/mod.rs +++ b/sea-orm-macros/src/derives/mod.rs @@ -11,6 +11,7 @@ mod migration; mod model; mod partial_model; mod primary_key; +mod related_entity; mod relation; mod try_getable_from_json; mod util; @@ -27,5 +28,6 @@ pub use migration::*; pub use model::*; pub use partial_model::*; pub use primary_key::*; +pub use related_entity::*; pub use relation::*; pub use try_getable_from_json::*; diff --git a/sea-orm-macros/src/derives/related_entity.rs b/sea-orm-macros/src/derives/related_entity.rs new file mode 100644 index 00000000..bf74a48c --- /dev/null +++ b/sea-orm-macros/src/derives/related_entity.rs @@ -0,0 +1,125 @@ +use heck::ToLowerCamelCase; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; + +use crate::derives::attributes::related_attr; + +enum Error { + InputNotEnum, + InvalidEntityPath, + Syn(syn::Error), +} + +struct DeriveRelatedEntity { + entity_ident: TokenStream, + ident: syn::Ident, + variants: syn::punctuated::Punctuated, +} + +impl DeriveRelatedEntity { + fn new(input: syn::DeriveInput) -> Result { + let sea_attr = related_attr::SeaOrm::try_from_attributes(&input.attrs) + .map_err(Error::Syn)? + .unwrap_or_default(); + + let ident = input.ident; + let entity_ident = match sea_attr.entity.as_ref().map(Self::parse_lit_string) { + Some(entity_ident) => entity_ident.map_err(|_| Error::InvalidEntityPath)?, + None => quote! { Entity }, + }; + + let variants = match input.data { + syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, + _ => return Err(Error::InputNotEnum), + }; + + Ok(DeriveRelatedEntity { + entity_ident, + ident, + variants, + }) + } + + fn expand(&self) -> syn::Result { + let ident = &self.ident; + let entity_ident = &self.entity_ident; + + let variant_implementations: Vec = self + .variants + .iter() + .map(|variant| { + let attr = related_attr::SeaOrm::from_attributes(&variant.attrs)?; + + let enum_name = &variant.ident; + + let target_entity = attr + .entity + .as_ref() + .map(Self::parse_lit_string) + .ok_or_else(|| { + syn::Error::new_spanned(variant, "Missing value for 'entity'") + })??; + + let def = match attr.def { + Some(def) => Some(Self::parse_lit_string(&def).map_err(|_| { + syn::Error::new_spanned(variant, "Missing value for 'def'") + })?), + None => None, + }; + + let name = enum_name.to_string().to_lower_camel_case(); + + if let Some(def) = def { + Result::<_, syn::Error>::Ok(quote! { + Self::#enum_name => builder.get_relation::<#entity_ident, #target_entity>(#name, #def) + }) + } else { + Result::<_, syn::Error>::Ok(quote! { + Self::#enum_name => via_builder.get_relation::<#entity_ident, #target_entity>(#name) + }) + } + + }) + .collect::, _>>()?; + + Ok(quote! { + impl seaography::RelationBuilder for #ident { + fn get_relation(&self, context: & 'static seaography::BuilderContext) -> async_graphql::dynamic::Field { + let builder = seaography::EntityObjectRelationBuilder { context }; + let via_builder = seaography::EntityObjectViaRelationBuilder { context }; + match self { + #(#variant_implementations,)* + _ => panic!("No relations for this entity"), + } + } + + } + }) + } + + fn parse_lit_string(lit: &syn::Lit) -> syn::Result { + match lit { + syn::Lit::Str(lit_str) => lit_str + .value() + .parse() + .map_err(|_| syn::Error::new_spanned(lit, "attribute not valid")), + _ => Err(syn::Error::new_spanned(lit, "attribute must be a string")), + } + } +} + +/// Method to derive a Related enumeration +pub fn expand_derive_related_entity(input: syn::DeriveInput) -> syn::Result { + let ident_span = input.ident.span(); + + match DeriveRelatedEntity::new(input) { + Ok(model) => model.expand(), + Err(Error::InputNotEnum) => Ok(quote_spanned! { + ident_span => compile_error!("you can only derive DeriveRelation on enums"); + }), + Err(Error::InvalidEntityPath) => Ok(quote_spanned! { + ident_span => compile_error!("invalid attribute value for 'entity'"); + }), + Err(Error::Syn(err)) => Err(err), + } +} diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index 23fb5aee..0e9c7b1b 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -639,6 +639,46 @@ pub fn derive_relation(input: TokenStream) -> TokenStream { .into() } +/// The DeriveRelatedEntity derive macro will implement seaography::RelationBuilder for RelatedEntity enumeration. +/// +/// ### Usage +/// +/// ```ignore +/// use sea_orm::entity::prelude::*; +/// +/// // ... +/// // Model, Relation enum, etc. +/// // ... +/// +/// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +/// pub enum RelatedEntity { +/// #[sea_orm(entity = "super::address::Entity")] +/// Address, +/// #[sea_orm(entity = "super::payment::Entity")] +/// Payment, +/// #[sea_orm(entity = "super::rental::Entity")] +/// Rental, +/// #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] +/// SelfRef, +/// #[sea_orm(entity = "super::store::Entity")] +/// Store, +/// #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] +/// SelfRefRev, +/// } +/// ``` +#[cfg(feature = "derive")] +#[proc_macro_derive(DeriveRelatedEntity, attributes(sea_orm))] +pub fn derive_related_entity(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + if cfg!(feature = "seaography") { + derives::expand_derive_related_entity(input) + .unwrap_or_else(Error::into_compile_error) + .into() + } else { + TokenStream::new() + } +} + /// The DeriveMigrationName derive macro will implement `sea_orm_migration::MigrationName` for a migration. /// /// ### Usage diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 18d17b79..394ab47d 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -12,7 +12,7 @@ pub use crate::{ pub use crate::{ DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel, DeriveModel, - DerivePrimaryKey, DeriveRelation, FromJsonQueryResult, + DerivePrimaryKey, DeriveRelatedEntity, DeriveRelation, FromJsonQueryResult, }; pub use async_trait; diff --git a/src/lib.rs b/src/lib.rs index 537e7f5b..15e13e59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -350,8 +350,8 @@ pub use schema::*; pub use sea_orm_macros::{ DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel, - DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey, DeriveRelation, - FromJsonQueryResult, FromQueryResult, + DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey, DeriveRelatedEntity, + DeriveRelation, FromJsonQueryResult, FromQueryResult, }; pub use sea_query; diff --git a/src/tests_cfg/cake_seaography.rs b/src/tests_cfg/cake_seaography.rs new file mode 100644 index 00000000..17441772 --- /dev/null +++ b/src/tests_cfg/cake_seaography.rs @@ -0,0 +1,64 @@ +use crate as sea_orm; +use crate::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "cake")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_name = "name", enum_name = "Name")] + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, + #[sea_orm( + has_many = "super::fruit::Entity", + on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# + )] + TropicalFruit, + #[sea_orm( + has_many = "super::fruit::Entity", + condition_type = "any", + on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# + )] + OrTropicalFruit, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::fruit::Entity")] + Fruit, + #[sea_orm(entity = "super::filling::Entity")] + Filling, + #[sea_orm( + entity = "super::fruit::Entity", + def = "Relation::TropicalFruit.def()" + )] + TropicalFruit, + #[sea_orm( + entity = "super::fruit::Entity", + def = "Relation::OrTropicalFruit.def()" + )] + OrTropicalFruit, +} + +impl ActiveModelBehavior for ActiveModel {}