Skip to content
This repository was archived by the owner on Jan 8, 2024. It is now read-only.

[BBO-290] Improve error handling of mutations#4

Merged
GiancarloJung merged 4 commits into
masterfrom
features/BBO-290
Oct 20, 2021
Merged

[BBO-290] Improve error handling of mutations#4
GiancarloJung merged 4 commits into
masterfrom
features/BBO-290

Conversation

@GiancarloJung

Copy link
Copy Markdown

Description

This PR modifies the Run method to handle errors in mutation requests, previously the mutations were always successful because the Errors field is in a different place inside the mutation responses.

Changes

  • Introduces method NewMutation to handle mutation operations
  • Encapsulates the original request struct to be used by queries and mutations
  • Updates the Run method to return errors if the operations is of the mutation type
  • Introduces mapstructure to format mutation responses from json using reflection

Notes

  • The changes were made only to json requests, not multipart forms
  • The Mutation only works for a single mutation, if we want to send multiple mutations on the same request we should still use the old request type.
  • Tests were also made using struct types for the responses, making this compatible with the way we build the responses in BFF

@pedrocunial pedrocunial left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As always, looks great! Left a suggestion that we might want to act on a later PR/fix! Gratz!

Comment thread error.go
errors := g.errors

if len(errors) > 0 {
return errors[len(errors)-1].ErrCode()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are graphql codes different than http codes status code? If so, the return of this function may be of """""""varying types""""""", which would reduce the interchangeability (is this even a word?) between our error types by their handlers.

My suggestion: Create a "code type" specific to our lib (it could be the graphql codes, http codes or even a new one) so that our callers can use it without needing to relly on some sort of reflection to check the error type/struct.

PS: This doesn't need to be done in this PR, it could very well be done in another one since it would be a very clearly breaking change.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphql errors are way more specific than normal http errors, some can be mapped to http errors, like 404, 500, etc but others are specific to the request (kyc returns bad_image when documents fail) I chose to leave this generic to enable this freedom that graphql gives.
we could implement the mapping of the most common fields inside our error utils or the client that implement this project, wdyt?

Comment thread graphql.go
Comment on lines +193 to +232
var gr *graphResponse
switch op.(type) {
case *Mutation:
var results struct {
Data map[string]graphMutationPayload
}

if err := json.NewDecoder(&buf).Decode(&results); err != nil {
return NewExecutionError(errors.Wrap(err, "decoding response"))
}
gr = &graphResponse{}

for _, result := range results.Data {
if !result.Successful {
messages := result.Messages
errors := make([]graphErr, len(messages))

for i, message := range messages {
errors[i] = graphErr{
Message: emptyOrString(message.Message),
Code: message.Code,
}
}

gr.Errors = append(gr.Errors, errors...)
} else {
err := mapstructure.Decode(results.Data, &resp)
if err != nil {
return NewExecutionError(errors.Wrap(err, "decoding response"))
}
}
// The code above only supports payloads with a single mutation
break
}

default:
gr = &graphResponse{Data: resp}
if err := json.NewDecoder(&buf).Decode(&gr); err != nil {
return NewExecutionError(errors.Wrap(err, "decoding response"))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: You could remove this usage of reflection by moving the decoding logic into a Decode method of each of your possible interfaces

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More specifically, the Decode implementation for the Mutation type would be the first case of this switch, and the default case would be the Request type implementation

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried setting up this way but I was faced with the same issue, when decoding resp we need to pass the structure where the data must be decoded, which at compile time would be of the type interface {}, because of this, I couldn't access the attributes of a concrete struct for example results.Data. Similarly, in test we use map[string]interface{} but we could also use a struct, in one of the test examples, and afaik there is no way of doing a direct conversion which means we needed to do some sort of reflection anyways. I would have ended up implementing something very similar to what this lib delivers.
The thing that I'm not happy about is that we go through the json twice, first to decode it as a mutationPayload and a second time to populate the resp =\

@GiancarloJung
GiancarloJung merged commit 0cd1373 into master Oct 20, 2021
@GiancarloJung
GiancarloJung deleted the features/BBO-290 branch October 20, 2021 19:30
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants