Micronaut GraphQL: How to respond with a non-200 HTTP status code from within GraphQL handler? - graphql

Following the docs and here's my exception handler (Kotlin):
#Produces
#Singleton
#Requirements(Requires(classes = [ForbiddenException::class, ExceptionHandler::class]))
class ForbiddenExceptionHandler : ExceptionHandler<ForbiddenException, HttpResponse<*>> {
override fun handle(request: HttpRequest<*>, exception: ForbiddenException): HttpResponse<*> {
return HttpResponse.status<String>(HttpStatus.FORBIDDEN, exception?.message)
}
}
Throwing a ForbiddenException from within my GraphQL handler bubbles the message into the response body, but the status code is always 200.
Example response:
{
"errors": [
{
"message": "Exception while fetching data (/createUser) : FORBIDDEN",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createUser"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": null
}
Micronaut version: 1.3.3
Micronaut GraphQL version: 1.3.0.RC1

Disclaimer:
GraphQL is not REST. You are here asking a question related to the core foundation of graphql specification (and any implementations of graphql in general).
They made the choice to embed most errors encountered in the execution of the queries but yet always return a 200 HTTP status. Therefore, you won't be able to change that in your project. It is not a configuration of graphql-java.
The good news is that the format of errors is known. Therefore, you are able to deserialize the error return payload in your application and handle correctly any error that would be thrown by graphql.
Please have a look at this link for in-depth explanations about the main difference between REST and Graphql.

Related

Are batch json-rpc requests not supported with near?

When trying to batch json rpc requests like this:
[
{
"jsonrpc":"2.0",
"id":"46500000",
"method":"block",
"params":{
"block_id": 46500000
}
},
{
"jsonrpc":"2.0",
"id":"46500001",
"method":"block",
"params":{
"block_id": 46500001
}
}
]
The response given is this:
{
"jsonrpc": "2.0",
"error": {
"name": "REQUEST_VALIDATION_ERROR",
"cause": {
"name": "PARSE_ERROR",
"info": {
"error_message": "JSON RPC Request format was expected"
}
},
"code": -32700,
"message": "Parse error",
"data": "JSON RPC Request format was expected"
},
"id": null
}
This is quite confusing since the above request is a valid jsonrpc request according to the spec. Are batch requests not supported on near?
You are right, batch JSON RPC requests are not supported by nearcore JSON RPC implementation. Batch JSON RPC requests can become arbitrary heavy. It is preferred to leverage load balancer to get several requests resolved, so just make separate calls instead of batching them.
I wonder what is your use-case, though. Maybe you want to take a look into Indexer Framework

How can we catch unauthorized exception which been thrown by quarkus

I am facing this problem but don't know how to achieve it.
I have a graphql endpoint to fetch list of user, it already enabled authentication check.
Basically, when I send a request fetchUsers without authorization header it will throw exception or status code to let the user know, but currently, it just response
{
"errors": [
{
"message": null,
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"fetchUsers"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"fetchUsers": null
}
}
And in the backend server, there have some exception throw:
SRGQL012000: Data Fetching Error: io.quarkus.security.UnauthorizedException
at io.quarkus.security.runtime.interceptor.check.AuthenticatedCheck.apply(AuthenticatedCheck.java:28)
at io.quarkus.security.runtime.interceptor.SecurityConstrainer.check(SecurityConstrainer.java:28)
at io.quarkus.security.runtime.interceptor.SecurityConstrainer_Subclass.check$$superforward1(SecurityConstrainer_Subclass.zig:100)
at io.quarkus.security.runtime.interceptor.SecurityConstrainer_Subclass$$function$$1.apply(SecurityConstrainer_Subclass$$function$$1.zig:41)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
Is there any way to catch this Unauthorized exception and custom it, to response 401 and the error message that we want to response.

When I send a mutation request to chaskiq graphql endpoint I get "Data not found"

I have been using Chaskiq for some work but ran into an error.
I built from source on Ubuntu 20.04.
I got the graphql part working and query requests work. However, whenever I make a mutation request I seem to get this response:
{
"errors": [
{
"message": "Data not found",
"data": {}
}
]
}
Example mutation request I sent to get the response above:
mutation updateAppUser($appKey: String!, $options: Json!, $id: Int!) {
updateAppUser(appKey: $appKey, options: $options, id: $id) {
appUser {
id
name
email
}
}
}
I have the variables Query Variables as below:
{
"appKey": <My_APP_KEY>,
"options": {
"name": <Custom_Name>
},
"id": <My_ID>
}
Please help me know the solution to the problem.
Data not found is returned when the server does not found any record.
basically ActiveRecord::RecordNotFound , so you are probably trying to find the wrong record. check the logs to see what's happening

Is it possible to accessing GraphQL validation errors from a Relay mutation?

I'm somewhat new to GraphQL, so, still piecing all moving parts together in my head.
On my server side I'm using TypeGraphQL which uses class-validator to perform validation of the queries coming in. On the client side I'm using Relay. When the validations fail, my commitMutation call in Relay calls onError and passes a string representation of the error, but the actual response from the server looks like this:
{
"errors": [
{
"message": "Argument Validation Error",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"updateCurrentUser"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"validationErrors": [
{
"target": {
"name": "ueoa",
"nickname": "ueoa",
"email": ""
},
"value": "",
"property": "email",
"children": [],
"constraints": {
"isEmail": "email must be an email"
}
}
],
"stacktrace": [
"Error: Argument Validation Error",
" at Object.validateArg (C:\\Users\\pupeno\\Documents\\Flexpoint Tech\\imok\\node_modules\\type-graphql\\dist\\resolvers\\validate-arg.js:24:15)",
" at runMicrotasks (<anonymous>)",
" at processTicksAndRejections (internal/process/task_queues.js:97:5)",
" at async Promise.all (index 0)"
]
}
}
}
],
"data": null
}
In this case, I left the email blank and thus on errors[0].extensions.exception.validationErrors[0].constraints.isEmail I have the error: "email must be an email".
Is there a way for Relay to let me access this structure to turn this errors into UI errors for the user? Or are these errors the equivalent of a 500 and I should implement my own separate error handling (equivalent of a 401)?
I do most of my validation on the client, but uniqueness can only be on done on the server and I'm trying to figure out the protocol between the two.
I don't know much about relay but I have used Typegraphql for sometime. What I can tell is that error from class-validator is nested differently from standard error (I am talking about throw new Error('this will be different'). I would advice for you to have an error formatter function on back-end so that regardless of type of error is thrown you can just return a standard graphql error. In apollo server there is option for formating error I believe other graphql servers has one too. Here is how it looks
const apolloServer = new ApolloServer({
formatError: (error) => error,
});
If class-validator's error is thrown error above will be ArgumentValidationError So if error is instance of ArgumentValidationError you need to proper format it and return to the client with all constraints values extracted and appended on message field. In this way all errors will behave the same on front-ent.
It is really hard to handle the error when it comes to graphQL as at the end of the result you will going to get 200 OK responses.
These errors you are getting are the INTERNAL SERVER ERROR, Equivalent to 500.
So, in this case, you'll need to handle it on your own.
As Nux wrote, Apollo has error handling modules, you can refer it from here This might be helpful.
Also As you mention that you are doing most of the validation from the client end, it is not a good idea to do validations only client-side as it can be brack and might become the Major breach.

graphql-java error data (like extensions) is being stuffed into "message" field on client

In graphql-java, I'm overriding getExtensions on a GraphQLError, in order to pass a "code" extension for Apollo Client to consume.
When I added a breakpoint, I can see that GraphQLObjectMapper sees the error in the executionResult with all its fields, including extensions properly set as expected.
However, when the error arrives at apollo, it gets strangely morphed and the entire error object (including the extensions array) appears to be stuffed into the string message field like so:
{
"errors": [
{
"message": "Unexpected error value: { message: \"Exception while fetching data (/myQuery) : Didn't work\", locations: [[Object]], path: [\"myQuery\"], extensions: { code: \"MY_CODE\", classification: \"DataFetchingException\" } }",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"mailLabels"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR"
}
}
],
"data": null
}
As far as I can tell apollo is not doing anything wrong. My suspicion is that graphql-java may be the one stuffing this entire error into the "message" field" and then setting the code as "INTERNAL_SERVER_ERROR", but I'm not really sure. Is there something I can do on the graphql-java end to prevent this and make it properly pass the extensions and not stuff them into the message value? Any assistance would be appreciated.
It is not caused by graphql-java but graphql-java-servlet. In some old version , by default it does not serialize GraphQLError to the structure defined by the GraphQL specification , which should be fixed in 7.5.1 in this issue.
If you cannot upgrade to the latest version , the simplest way is customize GraphQLObjectMapper and overriding its convertSanitizedExecutionResult :
public class CustomObjectMapper extends GraphQLObjectMapper {
#Override
public Map<String, Object> convertSanitizedExecutionResult(ExecutionResult executionResult, boolean includeData) {
Map<String, Object> result = super.convertSanitizedExecutionResult(executionResult, includeData);
if(result.containsKey("errors")){
result.put("errors", executionResult.getErrors().stream().map(err->err.toSpecification()).collect(toList()));
}
}
}

Resources