diff --git a/README.md b/README.md index 8b7375e..ee535aa 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,29 @@ mutation CreateDraftMutation { } ``` +- Delete a draft: + +```graphql +mutation DeleteDraftMutation { + deleteDraft(id: 1) { + __typename + ... on DeleteDraftSuccessResponse { + post { + id + title + } + } + ... on DeleteDraftErrorResponse { + error { + field + message + received + } + } + } +} +``` + - List existing drafts: ```graphql diff --git a/src/model/mod.rs b/src/model/mod.rs index 671c102..809afec 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,7 +5,7 @@ use sqlx::SqlitePool; use post::{ create_draft_mutation, delete_draft_mutation, drafts_query, posts_query, publish_mutation, - DeleteDraftResponse, Post, PublishResponse, + DeleteDraftResponse, Post, PublishResponse, ValidationError, }; pub(crate) type ServiceSchema = Schema; @@ -15,6 +15,8 @@ pub(crate) fn get_schema(db_pool: SqlitePool) -> ServiceSchema { .data(db_pool) // .limit_complexity(20) // may impact GraphQL Playground documentation // .limit_depth(5) // may impact GraphQL Playground documentation + // Registering ValidationError manually as it is not currently directly referenced + .register_output_type::() .finish() } diff --git a/src/model/post.rs b/src/model/post.rs index d99e58b..c61dd17 100644 --- a/src/model/post.rs +++ b/src/model/post.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use async_graphql::SimpleObject; +use async_graphql::{Interface, SimpleObject, Union}; use sqlx::SqlitePool; #[derive(Debug, PartialEq, SimpleObject)] @@ -11,8 +11,8 @@ pub struct Post { } #[derive(Debug, PartialEq, SimpleObject)] -/// Extra information on the input error -pub struct InputError { +/// Detail of the user input error +pub struct UserInputError { /// Field which had the invalid data field: String, @@ -23,22 +23,53 @@ pub struct InputError { received: String, } +/// Errors generated while parsing or validating input parameters +#[derive(Interface)] +#[graphql(field(name = "message", ty = "String"))] +pub enum ValidationError { + /// User input error, such as requesting an operation on a post with an `id` that does not + /// exist + UserInputError(UserInputError), +} + +/// Response sent on valid delete draft mutation +#[derive(Debug, PartialEq, SimpleObject)] +pub struct DeleteDraftSuccessResponse { + post: Post, +} + +/// Response sent on delete draft mutation with validation issues identified #[derive(Debug, PartialEq, SimpleObject)] -pub struct DeleteDraftResponse { - /// Deleted post - post: Option, +pub struct DeleteDraftErrorResponse { + error: UserInputError, +} - /// Input error details - error: Option, +/// Union of responses sent on delete draft mutation +#[derive(Debug, PartialEq, Union)] +pub enum DeleteDraftResponse { + DeleteDraftSuccessResponse(DeleteDraftSuccessResponse), + DeleteDraftErrorResponse(DeleteDraftErrorResponse), } +/// Response sent on valid publish draft mutation #[derive(Debug, PartialEq, SimpleObject)] -pub struct PublishResponse { +pub struct PublishSuccessResponse { /// Published post - post: Option, + post: Post, +} + +/// Response sent on publish draft mutation with validation issues identified +#[derive(Debug, PartialEq, SimpleObject)] +pub struct PublishErrorResponse { + /// User input error details + error: UserInputError, +} - /// Input error details - error: Option, +/// Union of responses sent on publish draft mutation +#[derive(Debug, PartialEq, Union)] +pub enum PublishResponse { + PublishSuccessResponse(PublishSuccessResponse), + PublishErrorResponse(PublishErrorResponse), } /// Return a list of up to 100 draft posts @@ -164,18 +195,18 @@ RETURNING })?; match deleted_row { - Some(value) => Ok(DeleteDraftResponse { - post: Some(value), - error: None, - }), - None => Ok(DeleteDraftResponse { - post: None, - error: Some(InputError { - field: "id".to_string(), - message: format!("Did not find draft post with id `{id}`"), - received: id.to_string(), - }), - }), + Some(value) => Ok(DeleteDraftResponse::DeleteDraftSuccessResponse( + DeleteDraftSuccessResponse { post: value }, + )), + None => Ok(DeleteDraftResponse::DeleteDraftErrorResponse( + DeleteDraftErrorResponse { + error: UserInputError { + field: "id".to_string(), + message: format!("Did not find draft post with id `{id}`"), + received: id.to_string(), + }, + }, + )), } } @@ -212,18 +243,18 @@ RETURNING })?; match updated_row { - Some(value) => Ok(PublishResponse { - post: Some(value), - error: None, - }), - None => Ok(PublishResponse { - post: None, - error: Some(InputError { - field: "id".to_string(), - message: format!("Did not find draft post with id `{id}`"), - received: id.to_string(), - }), - }), + Some(value) => Ok(PublishResponse::PublishSuccessResponse( + PublishSuccessResponse { post: value }, + )), + None => Ok(PublishResponse::PublishErrorResponse( + PublishErrorResponse { + error: UserInputError { + field: "id".to_string(), + message: format!("Did not find draft post with id `{id}`"), + received: id.to_string(), + }, + }, + )), } } @@ -234,8 +265,9 @@ mod tests { use crate::{ database::run_migrations, model::post::{ - create_draft_mutation, delete_draft_mutation, publish_mutation, DeleteDraftResponse, - InputError, + create_draft_mutation, delete_draft_mutation, publish_mutation, + DeleteDraftErrorResponse, DeleteDraftResponse, DeleteDraftSuccessResponse, + UserInputError, }, }; @@ -348,14 +380,13 @@ mod tests { // assert assert_eq!( outcome, - DeleteDraftResponse { - post: None, - error: Some(InputError { + DeleteDraftResponse::DeleteDraftErrorResponse(DeleteDraftErrorResponse { + error: UserInputError { field: String::from("id"), message: String::from("Did not find draft post with id `999`"), received: String::from("999") - }) - } + } + }) ); } @@ -368,7 +399,6 @@ mod tests { let Post { id, .. } = create_draft_mutation(&db_pool, &title, &body) .await .unwrap(); - //let _ = publish_mutation(&db_pool, id).await; // act let outcome = delete_draft_mutation(&db_pool, id).await.unwrap(); @@ -376,15 +406,14 @@ mod tests { // assert assert_eq!( outcome, - DeleteDraftResponse { - post: Some(Post { + DeleteDraftResponse::DeleteDraftSuccessResponse(DeleteDraftSuccessResponse { + post: Post { id, title, body, published: false - }), - error: None - } + }, + }) ); } }