Skip to content

Commit

Permalink
fix: 💫 improve GraphQL user errors
Browse files Browse the repository at this point in the history
  • Loading branch information
rodneylab committed Nov 6, 2024
1 parent b8b0bd4 commit b3ea51e
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 50 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<QueryRoot, MutationRoot, EmptySubscription>;
Expand All @@ -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::<ValidationError>()
.finish()
}

Expand Down
127 changes: 78 additions & 49 deletions src/model/post.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Context;
use async_graphql::SimpleObject;
use async_graphql::{Interface, SimpleObject, Union};
use sqlx::SqlitePool;

#[derive(Debug, PartialEq, SimpleObject)]
Expand All @@ -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,

Expand All @@ -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<Post>,
pub struct DeleteDraftErrorResponse {
error: UserInputError,
}

/// Input error details
error: Option<InputError>,
/// 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: 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<InputError>,
/// 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
Expand Down Expand Up @@ -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(),
},
},
)),
}
}

Expand Down Expand Up @@ -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(),
},
},
)),
}
}

Expand All @@ -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,
},
};

Expand Down Expand Up @@ -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")
})
}
}
})
);
}

Expand All @@ -368,23 +399,21 @@ 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();

// assert
assert_eq!(
outcome,
DeleteDraftResponse {
post: Some(Post {
DeleteDraftResponse::DeleteDraftSuccessResponse(DeleteDraftSuccessResponse {
post: Post {
id,
title,
body,
published: false
}),
error: None
}
},
})
);
}
}

0 comments on commit b3ea51e

Please sign in to comment.