Skip to content

Commit

Permalink
Fix Enum Types Handling (#181)
Browse files Browse the repository at this point in the history
* enum_type_name

* enum_type_name returns Option<&'static str>

* Added `register_active_enums!()` macro

* dep

* fmt

* bump sea-orm

* Fix
  • Loading branch information
billy1624 authored Dec 2, 2024
1 parent 126b232 commit 2ea76a5
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 99 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ categories = ["database"]

[dependencies]
async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] }
sea-orm = { version = "~1.1.0", default-features = false, features = ["seaography"] }
sea-orm = { version = "~1.1.2", default-features = false, features = ["seaography"] }
itertools = { version = "0.12.0" }
heck = { version = "0.4.1" }
thiserror = { version = "1.0.44" }
Expand All @@ -36,3 +36,7 @@ with-postgres-array = ["sea-orm/postgres-array"]
# with-mac_address = ["sea-orm/with-mac_address"]
field-snake-case = []
field-camel-case = []

# [patch.crates-io]
# sea-orm = { git = "https://github.com/SeaQL/sea-orm" }
# sea-orm-migration = { git = "https://github.com/SeaQL/sea-orm" }
2 changes: 1 addition & 1 deletion examples/mysql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ axum = { version = "0.7" }
async-graphql-axum = { version = "7.0" }
async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] }
dotenv = "0.15.0"
sea-orm = { version = "~1.1.0", features = ["sqlx-mysql", "runtime-async-std-native-tls", "seaography"] }
sea-orm = { version = "~1.1.2", features = ["sqlx-mysql", "runtime-async-std-native-tls", "seaography"] }
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17" }
Expand Down
2 changes: 1 addition & 1 deletion examples/postgres/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ poem = { version = "3.0" }
async-graphql-poem = { version = "7.0" }
async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] }
dotenv = "0.15.0"
sea-orm = { version = "~1.1.0", features = ["sqlx-postgres", "runtime-async-std-native-tls", "seaography"] }
sea-orm = { version = "~1.1.2", features = ["sqlx-postgres", "runtime-async-std-native-tls", "seaography"] }
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17" }
Expand Down
2 changes: 1 addition & 1 deletion examples/sqlite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ actix-web = { version = "4.5", default-features = false, features = ["macros"] }
async-graphql-actix-web = { version = "7.0" }
async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] }
dotenv = "0.15.0"
sea-orm = { version = "~1.1.0", features = ["sqlx-sqlite", "runtime-async-std-rustls", "seaography"] }
sea-orm = { version = "~1.1.2", features = ["sqlx-sqlite", "runtime-async-std-rustls", "seaography"] }
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17" }
Expand Down
2 changes: 1 addition & 1 deletion generator/src/templates/actix_cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ actix-web = { version = "4.5", default-features = false, features = ["macros"] }
async-graphql-actix-web = { version = "7.0" }
async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] }
dotenv = "0.15.0"
sea-orm = { version = "~1.1.0", features = ["<seaography-sql-library>", "runtime-async-std-native-tls", "seaography"] }
sea-orm = { version = "~1.1.2", features = ["<seaography-sql-library>", "runtime-async-std-native-tls", "seaography"] }
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17" }
Expand Down
2 changes: 1 addition & 1 deletion generator/src/templates/axum_cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ axum = { version = "0.7" }
async-graphql-axum = { version = "7.0" }
async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] }
dotenv = "0.15.0"
sea-orm = { version = "~1.1.0", features = ["<seaography-sql-library>", "runtime-async-std-native-tls", "seaography"] }
sea-orm = { version = "~1.1.2", features = ["<seaography-sql-library>", "runtime-async-std-native-tls", "seaography"] }
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17" }
Expand Down
2 changes: 1 addition & 1 deletion generator/src/templates/poem_cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ poem = { version = "3.0" }
async-graphql-poem = { version = "7.0" }
async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] }
dotenv = "0.15.0"
sea-orm = { version = "~1.1.0", features = ["<seaography-sql-library>", "runtime-async-std-native-tls", "seaography"] }
sea-orm = { version = "~1.1.2", features = ["<seaography-sql-library>", "runtime-async-std-native-tls", "seaography"] }
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17" }
Expand Down
10 changes: 10 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,13 @@ macro_rules! register_entity_modules {
}
};
}

#[macro_export]
macro_rules! register_active_enums {
([$($enum_paths:path),+ $(,)?]) => {
pub fn register_active_enums(mut builder: seaography::builder::Builder) -> seaography::builder::Builder {
$(builder.register_enumeration::<$enum_paths>();)*
builder
}
};
}
166 changes: 90 additions & 76 deletions src/builder_context/types_map.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{collections::BTreeMap, num::ParseIntError};

use async_graphql::dynamic::{TypeRef, ValueAccessor};
use heck::ToUpperCamelCase;
use sea_orm::{ColumnTrait, ColumnType, EntityTrait};

use crate::{ActiveEnumBuilder, BuilderContext, EntityObjectBuilder, SeaResult};
Expand Down Expand Up @@ -238,87 +239,100 @@ impl TypesMapHelper {
&self,
ty: &ColumnType,
not_null: bool,
enum_type_name: Option<&'static str>,
) -> Option<TypeRef> {
let active_enum_builder = ActiveEnumBuilder {
context: self.context,
};

match ty {
ColumnType::Char(_) | ColumnType::String(_) | ColumnType::Text => {
Some(TypeRef::named(TypeRef::STRING))
}
ColumnType::TinyInteger
| ColumnType::SmallInteger
| ColumnType::Integer
| ColumnType::BigInteger
| ColumnType::TinyUnsigned
| ColumnType::SmallUnsigned
| ColumnType::Unsigned
| ColumnType::BigUnsigned => Some(TypeRef::named(TypeRef::INT)),
ColumnType::Float | ColumnType::Double => Some(TypeRef::named(TypeRef::FLOAT)),
ColumnType::Decimal(_) | ColumnType::Money(_) => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::DateTime
| ColumnType::Timestamp
| ColumnType::TimestampWithTimeZone
| ColumnType::Time
| ColumnType::Date => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Year => Some(TypeRef::named(TypeRef::INT)),
ColumnType::Interval(_, _) => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Binary(_)
| ColumnType::VarBinary(_)
| ColumnType::Bit(_)
| ColumnType::VarBit(_)
| ColumnType::Blob => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Boolean => Some(TypeRef::named(TypeRef::BOOLEAN)),
// FIXME: support json type
ColumnType::Json | ColumnType::JsonBinary => None,
ColumnType::Uuid => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Enum {
name: enum_name,
variants: _,
} => Some(TypeRef::named(
active_enum_builder.type_name_from_iden(enum_name),
)),
ColumnType::Cidr | ColumnType::Inet | ColumnType::MacAddr => {
Some(TypeRef::named(TypeRef::STRING))
}
#[cfg(not(feature = "with-postgres-array"))]
ColumnType::Array(_) => None,
#[cfg(feature = "with-postgres-array")]
ColumnType::Array(iden) => {
// FIXME: Propagating the not_null flag here is probably incorrect. The following
// types are all logically valid:
// - [T]
// - [T!]
// - [T]!
// - [T!]!
// - [[T]]
// - [[T!]]
// - [[T]!]
// - [[T!]!]
// - [[T!]!]!
// - [[T!]]! (etc, recursively)
//
// This is true for both GraphQL itself but also for the equivalent types in some
// backends, like Postgres.
//
// However, the not_null flag lives on the column definition in sea_query, not on
// the type itself. That means we lose the ability to represent nullability
// reliably on any inner type. We have three options:
// - pass down the flag (what we're doing here):
// pros: likely the most common intent from those who care about nullability
// cons: can be incorrect in both inserts and queries
// - always pass true:
// pros: none? maybe inserts are easier to reason about?
// cons: just as likely to be wrong as flag passing
// - always pass false:
// pros: always technically workable for queries (annoying for non-null data)
// conts: bad for inserts
let iden_type = self.sea_orm_column_type_to_graphql_type(iden.as_ref(), true);
iden_type.map(|it| TypeRef::List(Box::new(it)))
}
ColumnType::Custom(_iden) => Some(TypeRef::named(TypeRef::STRING)),
_ => None,
match enum_type_name {
Some(enum_type_name) => Some(TypeRef::named(format!(
"{}Enum",
enum_type_name.to_upper_camel_case()
))),
None => match ty {
ColumnType::Char(_) | ColumnType::String(_) | ColumnType::Text => {
Some(TypeRef::named(TypeRef::STRING))
}
ColumnType::TinyInteger
| ColumnType::SmallInteger
| ColumnType::Integer
| ColumnType::BigInteger
| ColumnType::TinyUnsigned
| ColumnType::SmallUnsigned
| ColumnType::Unsigned
| ColumnType::BigUnsigned => Some(TypeRef::named(TypeRef::INT)),
ColumnType::Float | ColumnType::Double => Some(TypeRef::named(TypeRef::FLOAT)),
ColumnType::Decimal(_) | ColumnType::Money(_) => {
Some(TypeRef::named(TypeRef::STRING))
}
ColumnType::DateTime
| ColumnType::Timestamp
| ColumnType::TimestampWithTimeZone
| ColumnType::Time
| ColumnType::Date => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Year => Some(TypeRef::named(TypeRef::INT)),
ColumnType::Interval(_, _) => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Binary(_)
| ColumnType::VarBinary(_)
| ColumnType::Bit(_)
| ColumnType::VarBit(_)
| ColumnType::Blob => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Boolean => Some(TypeRef::named(TypeRef::BOOLEAN)),
// FIXME: support json type
ColumnType::Json | ColumnType::JsonBinary => None,
ColumnType::Uuid => Some(TypeRef::named(TypeRef::STRING)),
ColumnType::Enum {
name: enum_name,
variants: _,
} => Some(TypeRef::named(
active_enum_builder.type_name_from_iden(enum_name),
)),
ColumnType::Cidr | ColumnType::Inet | ColumnType::MacAddr => {
Some(TypeRef::named(TypeRef::STRING))
}
#[cfg(not(feature = "with-postgres-array"))]
ColumnType::Array(_) => None,
#[cfg(feature = "with-postgres-array")]
ColumnType::Array(iden) => {
// FIXME: Propagating the not_null flag here is probably incorrect. The following
// types are all logically valid:
// - [T]
// - [T!]
// - [T]!
// - [T!]!
// - [[T]]
// - [[T!]]
// - [[T]!]
// - [[T!]!]
// - [[T!]!]!
// - [[T!]]! (etc, recursively)
//
// This is true for both GraphQL itself but also for the equivalent types in some
// backends, like Postgres.
//
// However, the not_null flag lives on the column definition in sea_query, not on
// the type itself. That means we lose the ability to represent nullability
// reliably on any inner type. We have three options:
// - pass down the flag (what we're doing here):
// pros: likely the most common intent from those who care about nullability
// cons: can be incorrect in both inserts and queries
// - always pass true:
// pros: none? maybe inserts are easier to reason about?
// cons: just as likely to be wrong as flag passing
// - always pass false:
// pros: always technically workable for queries (annoying for non-null data)
// conts: bad for inserts
let iden_type = self.sea_orm_column_type_to_graphql_type(
iden.as_ref(),
true,
enum_type_name,
);
iden_type.map(|it| TypeRef::List(Box::new(it)))
}
ColumnType::Custom(_iden) => Some(TypeRef::named(TypeRef::STRING)),
_ => None,
},
}
.map(|ty| {
if not_null {
Expand Down
15 changes: 9 additions & 6 deletions src/enumerations/active_enum.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use async_graphql::dynamic::Enum;
use heck::{ToSnakeCase, ToUpperCamelCase};
use heck::ToUpperCamelCase;
use sea_orm::{ActiveEnum, DynIden, Value};

use crate::BuilderContext;
Expand All @@ -19,11 +19,7 @@ impl std::default::Default for ActiveEnumConfig {
format!("{}Enum", name.to_upper_camel_case())
}),
variant_name: Box::new(|_enum_name: &str, variant: &str| -> String {
if cfg!(feature = "field-snake-case") {
variant.to_snake_case()
} else {
variant.to_upper_camel_case().to_ascii_uppercase()
}
format_variant(variant)
}),
}
}
Expand Down Expand Up @@ -65,3 +61,10 @@ impl ActiveEnumBuilder {
})
}
}

pub(crate) fn format_variant(variant: &str) -> String {
variant
.chars()
.filter(|&c| c.is_ascii_alphanumeric() || c == '_')
.collect()
}
13 changes: 5 additions & 8 deletions src/inputs/active_enum_filter_input.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::collections::BTreeSet;

use async_graphql::dynamic::ObjectAccessor;
use heck::{ToSnakeCase, ToUpperCamelCase};
use heck::ToUpperCamelCase;
use sea_orm::{ActiveEnum, ColumnTrait, ColumnType, Condition, DynIden, EntityTrait};

use crate::{ActiveEnumBuilder, BuilderContext, FilterInfo, FilterOperation, SeaResult};
use crate::{
format_variant, ActiveEnumBuilder, BuilderContext, FilterInfo, FilterOperation, SeaResult,
};

/// The configuration structure for ActiveEnumFilterInputConfig
pub struct ActiveEnumFilterInputConfig {
Expand Down Expand Up @@ -88,12 +90,7 @@ where

let extract_variant = move |input: &str| -> String {
let variant = variants.iter().find(|variant| {
let variant = variant.to_string();
let variant = if cfg!(feature = "field-snake-case") {
variant.to_snake_case()
} else {
variant.to_upper_camel_case().to_ascii_uppercase()
};
let variant = format_variant(&variant.to_string());
variant.eq(input)
});
variant.unwrap().to_string()
Expand Down
2 changes: 2 additions & 0 deletions src/inputs/entity_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl EntityInputBuilder {
}

let column_def = column.def();
let enum_type_name = column.enum_type_name();

let auto_increment = match <T::PrimaryKey as PrimaryKeyToColumn>::from_column(column) {
Some(_) => T::PrimaryKey::auto_increment(),
Expand All @@ -107,6 +108,7 @@ impl EntityInputBuilder {
let graphql_type = match types_map_helper.sea_orm_column_type_to_graphql_type(
column_def.get_column_type(),
is_insert_not_nullable,
enum_type_name,
) {
Some(type_name) => type_name,
None => return object,
Expand Down
6 changes: 4 additions & 2 deletions src/outputs/entity_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl std::default::Default for EntityObjectConfig {
}
}

use crate::{BuilderContext, GuardAction, TypesMapHelper};
use crate::{format_variant, BuilderContext, GuardAction, TypesMapHelper};

/// This builder produces the GraphQL object of a SeaORM entity
pub struct EntityObjectBuilder {
Expand Down Expand Up @@ -112,10 +112,12 @@ impl EntityObjectBuilder {
let column_name = self.column_name::<T>(&column);

let column_def = column.def();
let enum_type_name = column.enum_type_name();

let graphql_type = match types_map_helper.sea_orm_column_type_to_graphql_type(
column_def.get_column_type(),
!column_def.is_null(),
enum_type_name,
) {
Some(type_name) => type_name,
None => return object,
Expand Down Expand Up @@ -213,7 +215,7 @@ fn sea_query_value_to_graphql_value(
sea_orm::Value::Float(value) => value.map(Value::from),
sea_orm::Value::Double(value) => value.map(Value::from),
sea_orm::Value::String(value) if is_enum => {
value.map(|it| Value::from(it.as_str().to_upper_camel_case().to_ascii_uppercase()))
value.map(|it| Value::from(format_variant(it.as_str())))
}
sea_orm::Value::String(value) => value.map(|it| Value::from(it.as_str())),
sea_orm::Value::Char(value) => value.map(|it| Value::from(it.to_string())),
Expand Down

0 comments on commit 2ea76a5

Please sign in to comment.