From d2ddd23a733c12f64e7aef9ecc645c18691b4d5e Mon Sep 17 00:00:00 2001 From: Justin Sievenpiper Date: Sat, 16 Dec 2023 12:04:34 -0800 Subject: [PATCH] Add Support For Postgres Vec (#159) * add support for postgres array --- Cargo.toml | 6 +- examples/postgres/Cargo.toml | 2 +- src/builder_context/types_map.rs | 755 ++++++++++++++++++------------- src/error.rs | 2 + src/inputs/entity_input.rs | 16 +- src/outputs/entity_object.rs | 420 ++++++----------- 6 files changed, 585 insertions(+), 616 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a146c91..59c4e3c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ categories = ["database"] [dependencies] async-graphql = { version = "6.0.7", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] } sea-orm = { version = "0.12.0", default-features = false, features = ["seaography"] } -itertools = { version = "0.11.0" } +itertools = { version = "0.12.0" } heck = { version = "0.4.1" } thiserror = { version = "1.0.44" } async-trait = { version = "0.1" } @@ -32,6 +32,6 @@ with-time = ["sea-orm/with-time", "async-graphql/time"] with-uuid = ["sea-orm/with-uuid"] with-decimal = ["sea-orm/with-rust_decimal", "async-graphql/decimal"] with-bigdecimal = ["sea-orm/with-bigdecimal", "async-graphql/bigdecimal"] -# with-postgres-array = ["sea-orm/postgres-array"] +with-postgres-array = ["sea-orm/postgres-array"] # with-ipnetwork = ["sea-orm/with-ipnetwork"] -# with-mac_address = ["sea-orm/with-mac_address"] +# with-mac_address = ["sea-orm/with-mac_address"] \ No newline at end of file diff --git a/examples/postgres/Cargo.toml b/examples/postgres/Cargo.toml index ba802680..4cf1be6f 100644 --- a/examples/postgres/Cargo.toml +++ b/examples/postgres/Cargo.toml @@ -18,7 +18,7 @@ lazy_static = { version = "1.4.0" } [dependencies.seaography] path = "../../" version = "^1.0.0-rc.2" # seaography version -features = ["with-decimal", "with-chrono"] +features = ["with-decimal", "with-chrono", "with-postgres-array"] [dev-dependencies] serde_json = { version = "1.0.103" } diff --git a/src/builder_context/types_map.rs b/src/builder_context/types_map.rs index 575304d8..e18d18cf 100644 --- a/src/builder_context/types_map.rs +++ b/src/builder_context/types_map.rs @@ -176,16 +176,13 @@ impl TypesMapHelper { variants: _variants, } => ConvertedType::Enum(name.to_string()), - #[cfg(not(feature = "postgres-array"))] + #[cfg(not(feature = "with-postgres-array"))] ColumnType::Array(_) => ConvertedType::String, - #[cfg(feature = "postgres-array")] + #[cfg(feature = "with-postgres-array")] ColumnType::Array(ty) => { - let inner = self.map_sea_orm_type_helper( - entity_name, - &format!("{}.array", column_name), - column_type, - ); - ConvertedType::Array(inner) + let inner = + self.get_column_type_helper(entity_name, &format!("{}.array", column_name), ty); + ConvertedType::Array(Box::new(inner)) } #[cfg(not(feature = "with-ipnetwork"))] @@ -229,310 +226,28 @@ impl TypesMapHelper { return parser.as_ref()(value); } - Ok(match self.get_column_type::(column) { - ConvertedType::Bool => value.boolean().map(|v| v.into())?, - ConvertedType::TinyInteger => { - let value: i8 = value.i64()?.try_into()?; - sea_orm::Value::TinyInt(Some(value)) - } - ConvertedType::SmallInteger => { - let value: i16 = value.i64()?.try_into()?; - sea_orm::Value::SmallInt(Some(value)) - } - ConvertedType::Integer => { - let value: i32 = value.i64()?.try_into()?; - sea_orm::Value::Int(Some(value)) - } - ConvertedType::BigInteger => { - let value = value.i64()?; - sea_orm::Value::BigInt(Some(value)) - } - ConvertedType::TinyUnsigned => { - let value: u8 = value.u64()?.try_into()?; - sea_orm::Value::TinyUnsigned(Some(value)) - } - ConvertedType::SmallUnsigned => { - let value: u16 = value.u64()?.try_into()?; - sea_orm::Value::SmallUnsigned(Some(value)) - } - ConvertedType::Unsigned => { - let value: u32 = value.u64()?.try_into()?; - sea_orm::Value::Unsigned(Some(value)) - } - ConvertedType::BigUnsigned => { - let value = value.u64()?; - sea_orm::Value::BigUnsigned(Some(value)) - } - ConvertedType::Float => { - let value = value.f32()?; - sea_orm::Value::Float(Some(value)) - } - ConvertedType::Double => { - let value = value.f64()?; - sea_orm::Value::Double(Some(value)) - } - ConvertedType::String | ConvertedType::Enum(_) | ConvertedType::Custom(_) => { - let value = value.string()?; - sea_orm::Value::String(Some(Box::new(value.to_string()))) - } - ConvertedType::Char => { - let value = value.string()?; - let value: char = match value.chars().next() { - Some(value) => value, - None => return Ok(sea_orm::Value::Char(None)), - }; - sea_orm::Value::Char(Some(value)) - } - ConvertedType::Bytes => { - let value = decode_hex(value.string()?)?; - sea_orm::Value::Bytes(Some(Box::new(value))) - } - #[cfg(feature = "with-json")] - ConvertedType::Json => { - use std::str::FromStr; - - let value = - sea_orm::entity::prelude::Json::from_str(value.string()?).map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("Json - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::Json(Some(Box::new(value))) - } - #[cfg(feature = "with-chrono")] - ConvertedType::ChronoDate => { - let value = sea_orm::entity::prelude::ChronoDate::parse_from_str( - value.string()?, - "%Y-%m-%d", - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("ChronoDate - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::ChronoDate(Some(Box::new(value))) - } - #[cfg(feature = "with-chrono")] - ConvertedType::ChronoTime => { - let value = sea_orm::entity::prelude::ChronoTime::parse_from_str( - value.string()?, - "%H:%M:%S", - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("ChronoTime - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::ChronoTime(Some(Box::new(value))) - } - #[cfg(feature = "with-chrono")] - ConvertedType::ChronoDateTime => { - let value = sea_orm::entity::prelude::ChronoDateTime::parse_from_str( - value.string()?, - "%Y-%m-%d %H:%M:%S", - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("ChronoDateTime - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::ChronoDateTime(Some(Box::new(value))) - } - #[cfg(feature = "with-chrono")] - ConvertedType::ChronoDateTimeUtc => { - use std::str::FromStr; - - let value = sea_orm::entity::prelude::ChronoDateTimeUtc::from_str(value.string()?) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("ChronoDateTimeUtc - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::ChronoDateTimeUtc(Some(Box::new(value))) - } - #[cfg(feature = "with-chrono")] - ConvertedType::ChronoDateTimeLocal => { - use std::str::FromStr; - - let value = - sea_orm::entity::prelude::ChronoDateTimeLocal::from_str(value.string()?) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("ChronoDateTimeLocal - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::ChronoDateTimeLocal(Some(Box::new(value))) - } - #[cfg(feature = "with-chrono")] - ConvertedType::ChronoDateTimeWithTimeZone => { - let value = sea_orm::entity::prelude::ChronoDateTimeWithTimeZone::parse_from_str( - value.string()?, - "%Y-%m-%d %H:%M:%S %:z", - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!( - "ChronoDateTimeWithTimeZone - {}.{}", - entity_name, column_name - ), - ) - })?; - - sea_orm::Value::ChronoDateTimeWithTimeZone(Some(Box::new(value))) - } - #[cfg(feature = "with-time")] - ConvertedType::TimeDate => { - use std::str::FromStr; - - let value = sea_orm::entity::prelude::TimeDate::parse( - value.string()?, - sea_orm::sea_query::value::time_format::FORMAT_DATE, - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("TimeDate - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::TimeDate(Some(Box::new(value))) - } - #[cfg(feature = "with-time")] - ConvertedType::TimeTime => { - use std::str::FromStr; - - let value = sea_orm::entity::prelude::TimeTime::parse( - value.string()?, - sea_orm::sea_query::value::time_format::FORMAT_TIME, - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("TimeTime - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::TimeTime(Some(Box::new(value))) - } - #[cfg(feature = "with-time")] - ConvertedType::TimeDateTime => { - use std::str::FromStr; - - let value = sea_orm::entity::prelude::TimeDateTime::parse( - value.string()?, - sea_orm::sea_query::value::time_format::FORMAT_DATETIME, - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("TimeDateTime - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::TimeDateTime(Some(Box::new(value))) - } - #[cfg(feature = "with-time")] - ConvertedType::TimeDateTimeWithTimeZone => { - use std::str::FromStr; - let value = sea_orm::entity::prelude::TimeDateTimeWithTimeZone::parse( - value.string()?, - sea_orm::sea_query::value::time_format::FORMAT_DATETIME_TZ, - ) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("TimeDateTimeWithTimeZone - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::TimeDateTimeWithTimeZone(Some(Box::new(value))) - } - #[cfg(feature = "with-uuid")] - ConvertedType::Uuid => { - use std::str::FromStr; - - let value = - sea_orm::entity::prelude::Uuid::from_str(value.string()?).map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("Uuid - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::Uuid(Some(Box::new(value))) - } - #[cfg(feature = "with-decimal")] - ConvertedType::Decimal => { - use std::str::FromStr; - - let value = - sea_orm::entity::prelude::Decimal::from_str(value.string()?).map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("Decimal - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::Decimal(Some(Box::new(value))) - } - #[cfg(feature = "with-bigdecimal")] - ConvertedType::BigDecimal => { - use std::str::FromStr; - - let value = sea_orm::entity::prelude::BigDecimal::from_str(value.string()?) - .map_err(|e| { - crate::SeaographyError::TypeConversionError( - e.to_string(), - format!("BigDecimal - {}.{}", entity_name, column_name), - ) - })?; - - sea_orm::Value::BigDecimal(Some(Box::new(value))) - } - // FIXME: support array type - #[cfg(feature = "postgres-array")] - ConvertedType::Array(ConvertedType) => { - let value = value.string()?; - sea_orm::Value::String(Some(Box::new(value.to_string()))) - } - // FIXME: support ip type - #[cfg(feature = "with-ipnetwork")] - ConvertedType::IpNetwork => { - let value = value.string()?; - sea_orm::Value::String(Some(Box::new(value.to_string()))) - } - // FIXME: support mac type - #[cfg(feature = "with-mac_address")] - ConvertedType::MacAddress => { - let value = value.string()?; - sea_orm::Value::String(Some(Box::new(value.to_string()))) - } - }) + converted_value_to_sea_orm_value( + &self.get_column_type::(column), + value, + &entity_name, + &column_name, + ) } /// used to map from a SeaORM column type to an async_graphql type /// None indicates that we do not support the type - pub fn sea_orm_column_type_to_graphql_type(&self, ty: &ColumnType) -> Option { + pub fn sea_orm_column_type_to_graphql_type( + &self, + ty: &ColumnType, + not_null: bool, + ) -> Option { let active_enum_builder = ActiveEnumBuilder { context: self.context, }; match ty { ColumnType::Char(_) | ColumnType::String(_) | ColumnType::Text => { - Some(TypeRef::STRING.into()) + Some(TypeRef::named(TypeRef::STRING)) } ColumnType::TinyInteger | ColumnType::SmallInteger @@ -541,36 +256,78 @@ impl TypesMapHelper { | ColumnType::TinyUnsigned | ColumnType::SmallUnsigned | ColumnType::Unsigned - | ColumnType::BigUnsigned => Some(TypeRef::INT.into()), - ColumnType::Float | ColumnType::Double => Some(TypeRef::FLOAT.into()), - ColumnType::Decimal(_) | ColumnType::Money(_) => Some(TypeRef::STRING.into()), + | 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::STRING.into()), - ColumnType::Year(_) => Some(TypeRef::INT.into()), - ColumnType::Interval(_, _) => Some(TypeRef::STRING.into()), + | 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(_) => Some(TypeRef::STRING.into()), - ColumnType::Boolean => Some(TypeRef::BOOLEAN.into()), + | ColumnType::VarBit(_) => 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::STRING.into()), + ColumnType::Uuid => Some(TypeRef::named(TypeRef::STRING)), ColumnType::Enum { name: enum_name, variants: _, - } => Some(active_enum_builder.type_name_from_iden(enum_name)), + } => Some(TypeRef::named( + active_enum_builder.type_name_from_iden(enum_name), + )), ColumnType::Cidr | ColumnType::Inet | ColumnType::MacAddr => { - Some(TypeRef::STRING.into()) + Some(TypeRef::named(TypeRef::STRING)) } - // FIXME: support array type + #[cfg(not(feature = "with-postgres-array"))] ColumnType::Array(_) => None, - ColumnType::Custom(_iden) => Some(TypeRef::STRING.into()), + #[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, } + .map(|ty| { + if not_null { + TypeRef::NonNull(Box::new(ty)) + } else { + ty + } + }) } } @@ -648,9 +405,9 @@ pub enum ConvertedType { #[cfg(feature = "with-bigdecimal")] #[cfg_attr(docsrs, doc(cfg(feature = "with-bigdecimal")))] BigDecimal, - #[cfg(feature = "postgres-array")] - #[cfg_attr(docsrs, doc(cfg(feature = "postgres-array")))] - Array(ConvertedType), + #[cfg(feature = "with-postgres-array")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-postgres-array")))] + Array(Box), #[cfg(feature = "with-ipnetwork")] #[cfg_attr(docsrs, doc(cfg(feature = "with-ipnetwork")))] IpNetwork, @@ -661,6 +418,368 @@ pub enum ConvertedType { Custom(String), } +pub fn converted_type_to_sea_orm_array_type( + converted_type: &ConvertedType, +) -> SeaResult { + match converted_type { + ConvertedType::Bool => Ok(sea_orm::sea_query::value::ArrayType::Bool), + ConvertedType::TinyInteger => Ok(sea_orm::sea_query::value::ArrayType::TinyInt), + ConvertedType::SmallInteger => Ok(sea_orm::sea_query::value::ArrayType::SmallInt), + ConvertedType::Integer => Ok(sea_orm::sea_query::value::ArrayType::Int), + ConvertedType::BigInteger => Ok(sea_orm::sea_query::value::ArrayType::BigInt), + ConvertedType::TinyUnsigned => Ok(sea_orm::sea_query::value::ArrayType::TinyUnsigned), + ConvertedType::SmallUnsigned => Ok(sea_orm::sea_query::value::ArrayType::SmallUnsigned), + ConvertedType::Unsigned => Ok(sea_orm::sea_query::value::ArrayType::Unsigned), + ConvertedType::BigUnsigned => Ok(sea_orm::sea_query::value::ArrayType::BigUnsigned), + ConvertedType::Float => Ok(sea_orm::sea_query::value::ArrayType::Float), + ConvertedType::Double => Ok(sea_orm::sea_query::value::ArrayType::Double), + ConvertedType::String => Ok(sea_orm::sea_query::value::ArrayType::String), + ConvertedType::Char => Ok(sea_orm::sea_query::value::ArrayType::Char), + ConvertedType::Bytes => Ok(sea_orm::sea_query::value::ArrayType::Bytes), + #[cfg(feature = "with-postgres-array")] + ConvertedType::Array(_) => Err(crate::SeaographyError::NestedArrayConversionError), + ConvertedType::Enum(_) => Ok(sea_orm::sea_query::value::ArrayType::String), + ConvertedType::Custom(_) => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-json")] + ConvertedType::Json => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDate => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoTime => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTime => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTimeUtc => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTimeLocal => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTimeWithTimeZone => { + Ok(sea_orm::sea_query::value::ArrayType::String) + } + #[cfg(feature = "with-time")] + ConvertedType::TimeDate => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-time")] + ConvertedType::TimeTime => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-time")] + ConvertedType::TimeDateTime => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-time")] + ConvertedType::TimeDateTimeWithTimeZone => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-uuid")] + ConvertedType::Uuid => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-decimal")] + ConvertedType::Decimal => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-bigdecimal")] + ConvertedType::BigDecimal => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-ipnetwork")] + ConvertedType::IpNetwork => Ok(sea_orm::sea_query::value::ArrayType::String), + #[cfg(feature = "with-mac_address")] + ConvertedType::MacAddress => Ok(sea_orm::sea_query::value::ArrayType::String), + } +} + +#[allow(unused_variables)] // some conversions behind feature flags need extra params here. +pub fn converted_value_to_sea_orm_value( + column_type: &ConvertedType, + value: &ValueAccessor, + entity_name: &str, + column_name: &str, +) -> SeaResult { + Ok(match column_type { + ConvertedType::Bool => value.boolean().map(|v| v.into())?, + ConvertedType::TinyInteger => { + let value: i8 = value.i64()?.try_into()?; + sea_orm::Value::TinyInt(Some(value)) + } + ConvertedType::SmallInteger => { + let value: i16 = value.i64()?.try_into()?; + sea_orm::Value::SmallInt(Some(value)) + } + ConvertedType::Integer => { + let value: i32 = value.i64()?.try_into()?; + sea_orm::Value::Int(Some(value)) + } + ConvertedType::BigInteger => { + let value = value.i64()?; + sea_orm::Value::BigInt(Some(value)) + } + ConvertedType::TinyUnsigned => { + let value: u8 = value.u64()?.try_into()?; + sea_orm::Value::TinyUnsigned(Some(value)) + } + ConvertedType::SmallUnsigned => { + let value: u16 = value.u64()?.try_into()?; + sea_orm::Value::SmallUnsigned(Some(value)) + } + ConvertedType::Unsigned => { + let value: u32 = value.u64()?.try_into()?; + sea_orm::Value::Unsigned(Some(value)) + } + ConvertedType::BigUnsigned => { + let value = value.u64()?; + sea_orm::Value::BigUnsigned(Some(value)) + } + ConvertedType::Float => { + let value = value.f32()?; + sea_orm::Value::Float(Some(value)) + } + ConvertedType::Double => { + let value = value.f64()?; + sea_orm::Value::Double(Some(value)) + } + ConvertedType::String | ConvertedType::Enum(_) | ConvertedType::Custom(_) => { + let value = value.string()?; + sea_orm::Value::String(Some(Box::new(value.to_string()))) + } + ConvertedType::Char => { + let value = value.string()?; + let value: char = match value.chars().next() { + Some(value) => value, + None => return Ok(sea_orm::Value::Char(None)), + }; + sea_orm::Value::Char(Some(value)) + } + ConvertedType::Bytes => { + let value = decode_hex(value.string()?)?; + sea_orm::Value::Bytes(Some(Box::new(value))) + } + #[cfg(feature = "with-json")] + ConvertedType::Json => { + use std::str::FromStr; + + let value = sea_orm::entity::prelude::Json::from_str(value.string()?).map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("Json - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::Json(Some(Box::new(value))) + } + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDate => { + let value = + sea_orm::entity::prelude::ChronoDate::parse_from_str(value.string()?, "%Y-%m-%d") + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("ChronoDate - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::ChronoDate(Some(Box::new(value))) + } + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoTime => { + let value = + sea_orm::entity::prelude::ChronoTime::parse_from_str(value.string()?, "%H:%M:%S") + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("ChronoTime - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::ChronoTime(Some(Box::new(value))) + } + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTime => { + let value = sea_orm::entity::prelude::ChronoDateTime::parse_from_str( + value.string()?, + "%Y-%m-%d %H:%M:%S", + ) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("ChronoDateTime - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::ChronoDateTime(Some(Box::new(value))) + } + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTimeUtc => { + use std::str::FromStr; + + let value = sea_orm::entity::prelude::ChronoDateTimeUtc::from_str(value.string()?) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("ChronoDateTimeUtc - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::ChronoDateTimeUtc(Some(Box::new(value))) + } + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTimeLocal => { + use std::str::FromStr; + + let value = sea_orm::entity::prelude::ChronoDateTimeLocal::from_str(value.string()?) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("ChronoDateTimeLocal - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::ChronoDateTimeLocal(Some(Box::new(value))) + } + #[cfg(feature = "with-chrono")] + ConvertedType::ChronoDateTimeWithTimeZone => { + let value = sea_orm::entity::prelude::ChronoDateTimeWithTimeZone::parse_from_str( + value.string()?, + "%Y-%m-%d %H:%M:%S %:z", + ) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!( + "ChronoDateTimeWithTimeZone - {}.{}", + entity_name, column_name + ), + ) + })?; + + sea_orm::Value::ChronoDateTimeWithTimeZone(Some(Box::new(value))) + } + #[cfg(feature = "with-time")] + ConvertedType::TimeDate => { + use std::str::FromStr; + + let value = sea_orm::entity::prelude::TimeDate::parse( + value.string()?, + sea_orm::sea_query::value::time_format::FORMAT_DATE, + ) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("TimeDate - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::TimeDate(Some(Box::new(value))) + } + #[cfg(feature = "with-time")] + ConvertedType::TimeTime => { + use std::str::FromStr; + + let value = sea_orm::entity::prelude::TimeTime::parse( + value.string()?, + sea_orm::sea_query::value::time_format::FORMAT_TIME, + ) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("TimeTime - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::TimeTime(Some(Box::new(value))) + } + #[cfg(feature = "with-time")] + ConvertedType::TimeDateTime => { + use std::str::FromStr; + + let value = sea_orm::entity::prelude::TimeDateTime::parse( + value.string()?, + sea_orm::sea_query::value::time_format::FORMAT_DATETIME, + ) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("TimeDateTime - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::TimeDateTime(Some(Box::new(value))) + } + #[cfg(feature = "with-time")] + ConvertedType::TimeDateTimeWithTimeZone => { + use std::str::FromStr; + let value = sea_orm::entity::prelude::TimeDateTimeWithTimeZone::parse( + value.string()?, + sea_orm::sea_query::value::time_format::FORMAT_DATETIME_TZ, + ) + .map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("TimeDateTimeWithTimeZone - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::TimeDateTimeWithTimeZone(Some(Box::new(value))) + } + #[cfg(feature = "with-uuid")] + ConvertedType::Uuid => { + use std::str::FromStr; + + let value = sea_orm::entity::prelude::Uuid::from_str(value.string()?).map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("Uuid - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::Uuid(Some(Box::new(value))) + } + #[cfg(feature = "with-decimal")] + ConvertedType::Decimal => { + use std::str::FromStr; + + let value = + sea_orm::entity::prelude::Decimal::from_str(value.string()?).map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("Decimal - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::Decimal(Some(Box::new(value))) + } + #[cfg(feature = "with-bigdecimal")] + ConvertedType::BigDecimal => { + use std::str::FromStr; + + let value = + sea_orm::entity::prelude::BigDecimal::from_str(value.string()?).map_err(|e| { + crate::SeaographyError::TypeConversionError( + e.to_string(), + format!("BigDecimal - {}.{}", entity_name, column_name), + ) + })?; + + sea_orm::Value::BigDecimal(Some(Box::new(value))) + } + #[cfg(feature = "with-postgres-array")] + ConvertedType::Array(ty) => { + let list_value = value + .list()? + .iter() + .map(|value| { + converted_value_to_sea_orm_value(&*ty, &value, entity_name, column_name) + }) + .collect::>>()?; + + sea_orm::Value::Array( + converted_type_to_sea_orm_array_type(&ty)?, + Some(Box::new(list_value)), + ) + } + // FIXME: support ip type + #[cfg(feature = "with-ipnetwork")] + ConvertedType::IpNetwork => { + let value = value.string()?; + sea_orm::Value::String(Some(Box::new(value.to_string()))) + } + // FIXME: support mac type + #[cfg(feature = "with-mac_address")] + ConvertedType::MacAddress => { + let value = value.string()?; + sea_orm::Value::String(Some(Box::new(value.to_string()))) + } + }) +} + pub fn decode_hex(s: &str) -> Result, ParseIntError> { (0..s.len()) .step_by(2) diff --git a/src/error.rs b/src/error.rs index 52a4fa13..e2966ee6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,8 @@ pub enum SeaographyError { ParseIntError(#[from] std::num::ParseIntError), #[error("[type conversion: {1}] {0}")] TypeConversionError(String, String), + #[error("[array conversion] postgres array can not be nested type of array")] + NestedArrayConversionError, } impl From for SeaographyError { diff --git a/src/inputs/entity_input.rs b/src/inputs/entity_input.rs index 241f106e..2a08503d 100644 --- a/src/inputs/entity_input.rs +++ b/src/inputs/entity_input.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use async_graphql::dynamic::{InputObject, InputValue, ObjectAccessor, TypeRef}; +use async_graphql::dynamic::{InputObject, InputValue, ObjectAccessor}; use sea_orm::{ColumnTrait, EntityTrait, Iterable}; use crate::{BuilderContext, EntityObjectBuilder, SeaResult, TypesMapHelper}; @@ -96,20 +96,14 @@ impl EntityInputBuilder { let column_def = column.def(); - let type_name = - types_map_helper.sea_orm_column_type_to_graphql_type(column_def.get_column_type()); - - let type_name = match 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() && is_insert, + ) { Some(type_name) => type_name, None => return object, }; - let graphql_type = if column_def.is_null() || !is_insert { - TypeRef::named(type_name) - } else { - TypeRef::named_nn(type_name) - }; - object.field(InputValue::new(column_name, graphql_type)) }) } diff --git a/src/outputs/entity_object.rs b/src/outputs/entity_object.rs index 92371996..4ee08a93 100644 --- a/src/outputs/entity_object.rs +++ b/src/outputs/entity_object.rs @@ -1,4 +1,4 @@ -use async_graphql::dynamic::{Field, FieldFuture, Object, TypeRef}; +use async_graphql::dynamic::{Field, FieldFuture, Object}; use async_graphql::{Error, Value}; use heck::{ToLowerCamelCase, ToUpperCamelCase}; use sea_orm::{ColumnTrait, ColumnType, EntityName, EntityTrait, IdenStatic, Iterable, ModelTrait}; @@ -109,27 +109,26 @@ impl EntityObjectBuilder { let column_def = column.def(); - let type_name = match types_map_helper - .sea_orm_column_type_to_graphql_type(column_def.get_column_type()) - { + let graphql_type = match types_map_helper.sea_orm_column_type_to_graphql_type( + column_def.get_column_type(), + !column_def.is_null(), + ) { Some(type_name) => type_name, None => return object, }; - let graphql_type = if column_def.is_null() { - TypeRef::named(type_name) - } else { - TypeRef::named_nn(type_name) + // This isn't the most beautiful flag: it's indicating whether the leaf type is an + // enum, rather than the type itself. Ideally we'd only calculate this for the leaf + // type itself. Could be a good candidate for refactor as this code evolves to support + // more container types. For example, this at the very least should be recursive on + // Array types such that arrays of arrays of enums would be resolved correctly. + let is_enum: bool = match column_def.get_column_type() { + ColumnType::Enum { .. } => true, + #[cfg(feature = "with-postgres-array")] + ColumnType::Array(inner) => matches!(inner.as_ref(), ColumnType::Enum { .. }), + _ => false, }; - let is_enum: bool = matches!( - column_def.get_column_type(), - ColumnType::Enum { - name: _, - variants: _ - } - ); - let guard = self .context .guards @@ -180,276 +179,131 @@ impl EntityObjectBuilder { }); } - match object.get(column) { - sea_orm::sea_query::Value::Bool(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::TinyInt(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::SmallInt(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::Int(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::BigInt(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::TinyUnsigned(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }) - } - sea_orm::sea_query::Value::SmallUnsigned(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }) - } - sea_orm::sea_query::Value::Unsigned(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::BigUnsigned(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::Float(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::Double(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value))), - None => Ok(None), - } - }), - sea_orm::sea_query::Value::String(value) => FieldFuture::new(async move { - match value { - Some(value) => { - if is_enum { - Ok(Some(Value::from( - value.as_str().to_upper_camel_case().to_ascii_uppercase(), - ))) - } else { - Ok(Some(Value::from(value.as_str()))) - } - } - None => Ok(None), - } - }), - sea_orm::sea_query::Value::Char(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - #[allow(clippy::box_collection)] - sea_orm::sea_query::Value::Bytes(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(String::from_utf8_lossy(&value)))), - None => Ok(None), - } - }), - #[cfg(feature = "with-json")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))] - sea_orm::sea_query::Value::Json(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-chrono")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] - sea_orm::sea_query::Value::ChronoDate(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-chrono")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] - sea_orm::sea_query::Value::ChronoTime(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-chrono")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] - sea_orm::sea_query::Value::ChronoDateTime(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }) - } - - #[cfg(feature = "with-chrono")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] - sea_orm::sea_query::Value::ChronoDateTimeUtc(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }) - } - - #[cfg(feature = "with-chrono")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] - sea_orm::sea_query::Value::ChronoDateTimeLocal(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }) - } - - #[cfg(feature = "with-chrono")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] - sea_orm::sea_query::Value::ChronoDateTimeWithTimeZone(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }) - } - - #[cfg(feature = "with-time")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] - sea_orm::sea_query::Value::TimeDate(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-time")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] - sea_orm::sea_query::Value::TimeTime(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-time")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] - sea_orm::sea_query::Value::TimeDateTime(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }) - } - - #[cfg(feature = "with-time")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] - sea_orm::sea_query::Value::TimeDateTimeWithTimeZone(value) => { - FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }) - } - - #[cfg(feature = "with-uuid")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] - sea_orm::sea_query::Value::Uuid(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-decimal")] - sea_orm::sea_query::Value::Decimal(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-bigdecimal")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-bigdecimal")))] - sea_orm::sea_query::Value::BigDecimal(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "postgres-array")] - #[cfg_attr(docsrs, doc(cfg(feature = "postgres-array")))] - sea_orm::sea_query::Value::Array(array_type, value) => { - FieldFuture::new(async move { - // FIXME: test array type - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }) - } - - #[cfg(feature = "with-ipnetwork")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-ipnetwork")))] - sea_orm::sea_query::Value::IpNetwork(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - - #[cfg(feature = "with-mac_address")] - #[cfg_attr(docsrs, doc(cfg(feature = "with-mac_address")))] - sea_orm::sea_query::Value::MacAddress(value) => FieldFuture::new(async move { - match value { - Some(value) => Ok(Some(Value::from(value.to_string()))), - None => Ok(None), - } - }), - #[allow(unreachable_patterns)] - _ => panic!("Cannot convert SeaORM value"), - } + FieldFuture::new(async move { + Ok(sea_query_value_to_graphql_value( + object.get(column), + is_enum, + )) + }) }); object.field(field) }) } } + +fn sea_query_value_to_graphql_value( + sea_query_value: sea_orm::sea_query::Value, + is_enum: bool, +) -> Option { + match sea_query_value { + sea_orm::Value::Bool(value) => value.map(Value::from), + sea_orm::Value::TinyInt(value) => value.map(Value::from), + sea_orm::Value::SmallInt(value) => value.map(Value::from), + sea_orm::Value::Int(value) => value.map(Value::from), + sea_orm::Value::BigInt(value) => value.map(Value::from), + sea_orm::Value::TinyUnsigned(value) => value.map(Value::from), + sea_orm::Value::SmallUnsigned(value) => value.map(Value::from), + sea_orm::Value::Unsigned(value) => value.map(Value::from), + sea_orm::Value::BigUnsigned(value) => value.map(Value::from), + 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())) + } + 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())), + + #[allow(clippy::box_collection)] + sea_orm::Value::Bytes(value) => value.map(|it| Value::from(String::from_utf8_lossy(&it))), + + #[cfg(feature = "with-postgres-array")] + sea_orm::Value::Array(_array_value, value) => value.map(|it| { + Value::List( + it.into_iter() + .map(|item| { + sea_query_value_to_graphql_value(item, is_enum).unwrap_or(Value::Null) + }) + .collect(), + ) + }), + + #[cfg(feature = "with-json")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))] + sea_orm::sea_query::Value::Json(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + sea_orm::sea_query::Value::ChronoDate(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + sea_orm::sea_query::Value::ChronoTime(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + sea_orm::sea_query::Value::ChronoDateTime(value) => { + value.map(|it| Value::from(it.to_string())) + } + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + sea_orm::sea_query::Value::ChronoDateTimeUtc(value) => { + value.map(|it| Value::from(it.to_string())) + } + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + sea_orm::sea_query::Value::ChronoDateTimeLocal(value) => { + value.map(|it| Value::from(it.to_string())) + } + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + sea_orm::sea_query::Value::ChronoDateTimeWithTimeZone(value) => { + value.map(|it| Value::from(it.to_string())) + } + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + sea_orm::sea_query::Value::TimeDate(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + sea_orm::sea_query::Value::TimeTime(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + sea_orm::sea_query::Value::TimeDateTime(value) => { + value.map(|it| Value::from(it.to_string())) + } + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + sea_orm::sea_query::Value::TimeDateTimeWithTimeZone(value) => { + value.map(|it| Value::from(it.to_string())) + } + + #[cfg(feature = "with-uuid")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] + sea_orm::sea_query::Value::Uuid(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-decimal")] + sea_orm::sea_query::Value::Decimal(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-bigdecimal")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-bigdecimal")))] + sea_orm::sea_query::Value::BigDecimal(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-ipnetwork")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-ipnetwork")))] + sea_orm::sea_query::Value::IpNetwork(value) => value.map(|it| Value::from(it.to_string())), + + #[cfg(feature = "with-mac_address")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-mac_address")))] + sea_orm::sea_query::Value::MacAddress(value) => value.map(|it| Value::from(it.to_string())), + + #[allow(unreachable_patterns)] + _ => panic!("Cannot convert SeaORM value"), + } +}