SeaQL / seaography

🧭 GraphQL framework for SeaORM
Apache License 2.0
375 stars 35 forks source link

Update mutation breaks when using Postgres native enums #158

Open guidomb opened 9 months ago

guidomb commented 9 months ago

Description

If I have an entity with a field of type enum that's mapped to a native enum type in Postgres. When trying to update the entity's enum field via an update mutation and providing the value as a native GraphQL enum, the mutation panics when executed. To avoid the crash the value must be passed as string but that makes the explorer display an error and (per my understanding) this is incorrect.

author.rs

//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.6

use sea_orm::entity::prelude::*;
use serde::Serialize;

use crate::AuthorRole;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize)]
#[sea_orm(table_name = "author")]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub id: Uuid,
    #[sea_orm(column_type = "Text", unique)]
    pub email: String,
    #[sea_orm(column_type = "Text")]
    pub first_name: String,
    #[sea_orm(column_type = "Text")]
    pub last_name: String,
    pub created_at: DateTimeWithTimeZone,
    #[sea_orm(unique)]
    pub username: String,
    pub role: AuthorRole,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(has_many = "super::post::Entity")]
    Posts,
}

impl Related<super::post::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::Posts.def()
    }
}

impl ActiveModelBehavior for ActiveModel {}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
pub enum RelatedEntity {
    #[sea_orm(entity = "super::post::Entity")]
    Post,
}

author_role.rs

#[derive(
    EnumIter, DeriveActiveEnum, PartialEq, Eq, Clone, Copy, Debug, Serialize, async_graphql::Enum,
)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "author_role")]
#[serde(rename_all = "UPPERCASE")]
pub enum AuthorRole {
    #[sea_orm(string_value = "WRITTER")]
    Writter,
    #[sea_orm(string_value = "PUBLISHER")]
    Publisher,
    #[sea_orm(string_value = "EDITOR")]
    Editor,
}

migration

use entities::*;
use sea_orm_migration::{
    prelude::*,
    sea_orm::{EntityTrait, Set},
};

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                Table::create()
                    .table(Author)
                    .if_not_exists()
                    .col(
                        ColumnDef::new(author::Column::Id)
                            .uuid()
                            .extra("DEFAULT gen_random_uuid()")
                            .not_null()
                            .primary_key(),
                    )
                    .col(
                        ColumnDef::new(author::Column::Email)
                            .string_len(254)
                            .unique_key()
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(author::Column::FirstName)
                            .string_len(100)
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(author::Column::LastName)
                            .string_len(100)
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(author::Column::Username)
                            .string_len(50)
                            .unique_key()
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(post::Column::CreatedAt)
                            .timestamp_with_time_zone()
                            .extra("DEFAULT now()")
                            .not_null(),
                    )
                    .to_owned(),
            )
            .await?;
        manager
            .create_table(
                Table::create()
                    .table(Post)
                    .if_not_exists()
                    .col(
                        ColumnDef::new(post::Column::Id)
                            .uuid()
                            .extra("DEFAULT gen_random_uuid()")
                            .not_null()
                            .primary_key(),
                    )
                    .col(
                        ColumnDef::new(post::Column::Title)
                            .string_len(100)
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(post::Column::Description)
                            .string_len(500)
                            .not_null(),
                    )
                    .col(ColumnDef::new(post::Column::Content).text().not_null())
                    .col(
                        ColumnDef::new(post::Column::CreatedAt)
                            .timestamp_with_time_zone()
                            .extra("DEFAULT now()")
                            .not_null(),
                    )
                    .col(ColumnDef::new(post::Column::AuthorId).uuid().not_null())
                    .foreign_key(
                        ForeignKeyCreateStatement::new()
                            .name("post_author")
                            .from(Post, post::Column::AuthorId)
                            .to(Author, author::Column::Id),
                    )
                    .to_owned(),
            )
            .await?;

        // Seeding tables
        let result = Author::insert(author::ActiveModel {
            email: Set("guido@cedalio.com".into()),
            first_name: Set("Guido".into()),
            last_name: Set("Marucci Blas".into()),
            username: Set("guidomb".into()),
            ..Default::default()
        })
        .exec(manager.get_connection())
        .await?;
        let author_01_id = result.last_insert_id;

        let result = Author::insert(author::ActiveModel {
            email: Set("nico@cedalio.com".into()),
            first_name: Set("Nicolás".into()),
            last_name: Set("Magni".into()),
            username: Set("nicomagni".into()),
            ..Default::default()
        })
        .exec(manager.get_connection())
        .await?;
        let author_02_id = result.last_insert_id;

        let posts = vec![
            post::ActiveModel {
                title: Set("Some example post".into()),
                description: Set("This is an example post used for debugging".into()),
                content: Set("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".into()),
                author_id: Set(author_01_id),
                ..Default::default()
            },
            post::ActiveModel {
                title: Set("Another example post".into()),
                description: Set("This is another example post used for debugging".into()),
                content: Set("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".into()),
                author_id: Set(author_02_id),
                ..Default::default()
            }
        ];
        Post::insert_many(posts)
            .exec(manager.get_connection())
            .await?;

        Ok(())
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(Post).to_owned())
            .await?;
        manager
            .drop_table(Table::drop().table(Author).to_owned())
            .await?;

        Ok(())
    }
}

error

{
  "data": null,
  "errors": [
    {
      "message": "[async_graphql] Error { message: \"internal: not a string\", extensions: None }",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ]
}

Screenshot 2023-11-29 at 13 09 45 Screenshot 2023-11-29 at 13 10 13

Steps to Reproduce

  1. Define an entity with a field of type enum that's mapped to a native Postgres enum
  2. Update the entity field of type enum via an update mutation providing a value as a native GraphQL enum

Expected Behavior

The field gets updated

Actual Behavior

The mutation fails

Reproduces How Often

Always

Versions

source = "git+https://github.com/SeaQL/seaography.git#9c43f72017fb27bd5e2602c553ee59acc45ae26e"

Additional Information

YiNNx commented 9 months ago

I also encountered this problem. But for my case, changing the enum value to the string can make the mutation operation successful.