Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strongly typed errors (part of the graph) #92

Open
leebenson opened this issue Mar 26, 2022 · 2 comments
Open

Strongly typed errors (part of the graph) #92

leebenson opened this issue Mar 26, 2022 · 2 comments

Comments

@leebenson
Copy link

Is your feature request related to a problem? Please describe.

Instead of returning an errors object when validation fails with the typical 'extensions' key, I'd like to convert a mutation return type into a union of TSuccess | FormError[], where FormError contains a strongly typed { field: String!, error: String! }.

Describe the solution you'd like

Parsing errors in a front-end client is difficult, because its shape is unknown. It could be a form error, an authentication issue, an internal server error or something else. Many web GraphQL clients treat it as a 'hard' error by default.

I'd like to validate user input, converting 'errors' into a new FormError type. Where validation on user input is relevant, the return type should be modified to include a union accommodating both the 'success' type(s) and the potential for a user error.

i.e. I'd like to wind up with something like this:

mutation LoginWithEmailAndPassword($input: LoginWithEmailAndPasswordInput!) {
  loginWithEmailAndPassword(input: $input) {
    # When user input fails... e.g. missing email address, bad password format, etc
    ... on FormError {
      field
      error
    }
    # This is the 'success' response
    ... on LoginWithEmailAndPasswordSuccess {
      jwt
    }
    # This is also a legit. response... in this case, validation passed, but
    # logging in the user failed
    ... on LoginWithEmailAndPasswordFailure {
      error
    }
  }
}

... so that if an errors does occur, it generally means something more severe than user input is off!

Describe alternatives you've considered

I could alternatively create a wrapper for a GraphQL client (I'm using React Query, FWIW) that looks for 400 responses, parses the errors key, and determines the type of error.

But this has two drawbacks:

  • The wrapper itself isn't type safe. I'm doing a lot of manual/dynamic key checking.
  • It's not supported out the box by GraphQL Code Generator - I'm using this to generate strongly typed clients, and errors are typed as TError: unknown by default.

Another alternative is to write a helper in C# that validates in the function, and converts errors into a standard FormError type. This is fine, but it requires modifying the signature of a mutation to accommodate every use-case where input is used... which is a little repetitive.

Additional context

User input errors are common, and IMO relying on the errors key with a dynamic/untyped extensions meta to determine the kind of error makes it difficult to parse what is a normal part of users interacting with the API.

Rather than creating a wrapper that catches errors and handles them at the top-level, I'd rather make form errors a first-class feature of my graph. Fluent Validation is ideal for that, but I'm unsure whether there's a simple way to have this library modify the graph to allow for a union of a 'success' and FormError[] response where it's used, or I should focus instead on creating a wrapper.

In any case, thanks for a great 'glue' library! It's very useful indeed.

@sergeyshaykhullin
Copy link
Member

I did already some attempts to implement this approach, but where are a lot of edge cases(e.g. multiple inputs, error types):

If you want to use domain errors you can try HC 12 Mutation conventions https://chillicream.com/docs/hotchocolate/defining-a-schema/mutations#input-and-payload

If you have an idea how to implement this without breaking existing codebases, please share with me :)

@leebenson
Copy link
Author

Thanks for the update 👍🏻

The implementation I was thinking of if I did this myself was something like:

public ISomeReturnType ResolverMethod([Validation(typeof(SomeAbstractValidator))] CreateUserInput input) {}

The idea being, an AbstractValidator<T> could be provided to the attribute. Whenever input was created on a new query, it would run something akin to:

var validation = await input.ValidateAsync(input);
if (!validation.IsValid)
{
    return validation.ToGraphQLFormErrors()
}

The ISomeReturnType would need to be a union that contains a FormErrors type to compile.

I'm still trying to parse the internals of Hot Chocolate and understand how its event API works for input. It seems like it should be simple to do validation and short-circuit by returning a FormErrors type if validation fails, in much the same way that you're currently raising an error... but I'm still following code paths to understand how that works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants