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

Add extensions field to error response #171

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

ima9dan
Copy link

@ima9dan ima9dan commented Dec 6, 2021

Change 1: Added extensions to the error response.
Change 2: Added debug option to GraphQL Configuration (flag to output exception information to extensions)
Change 3: Moved serialize (), which was defined as an extension of GraphQLError, into GraphQLError. option


Change 1: Added extensions to the error response.

fields in extensions.

  • type: Error type. String type. Required. for client side. On the client side, we often want to branch the process depending on the error type. The default is "INTERNAL_SERVER_ERROR"
  • detail: Error details. Map <String, Any?>? Type. Optional. You can use it freely, but for example, you can return the Validation information of each Field as a map.
  • debug: Output detailed information of exception. It is output automatically when the debug option is set to true in GraphQL Configuration. This is explained in "Change 2".

ex1:

throw GraphQLError("validation error!","VALIDATION_ERROR",
    mapOf<String,Any?>("singleCheck" to mapOf<String,String>("email" to "not an email", "age" to "Limited to 150"), 
                        "multiCheck" to "The 'from' number must not exceed the 'to' number"))

output

{
  "errors": [
    {
      "message": "validation error!",
      "locations": [],
      "path": [],
      "extensions": {
        "type": "VALIDATION_ERROR",
        "detail": {
          "singleCheck": {
            "email": "not an email",
            "age": "Limited to 150"
          },
          "multiCheck": "The 'from' number must not exceed the 'to' number"
        }
      }
    }
  ]
}

ex2:

throw GraphQLError("Access Token has expired","AUTHORIZATION_ERROR")

output

{
  "errors": [
    {
      "message": "Access Token has expired",
      "locations": [],
      "path": [],
      "extensions": {
        "type": "AUTHORIZATION_ERROR",
      }
    }
  ]
}

Change 2: Added debug option to GraphQL Configuration (flag to output exception information to extensions)

First, set the debug option to true when installing GraphQL

install(GraphQL) {
    useDefaultPrettyPrinter = true
    playground = true
    debug = true   // added option
    endpoint = "/"

    wrap {
        authenticate(optional = true, build = it)
    }
    context { call ->
        call.authentication.principal<User>()?.let {
            +it
        }
    }
    schema { ufoSchema() }
}

Then raise GraphQLError

throw GraphQLError("Access Token has expired","AUTHORIZATION_ERROR")

Finaly, the debug field is automatically added to extensions, and the detailed information of exception is output.

{
  "errors": [
    {
      "message": "Access Token has expired",
      "locations": [],
      "path": [],
      "extensions": {
        "type": "AUTHORIZATION_ERROR",
        "debug": {
          "fileName": "GraphQLSchema.kt",
          "line": "38",
          "method": "invokeSuspend",
          "classPath": "com.apurebase.kgraphql.GraphQLSchemaKt$ufoSchema$3$1",
          "stackTrace": [
            "com.apurebase.kgraphql.GraphQLSchemaKt$ufoSchema$3$1.invokeSuspend(GraphQLSchema.kt:38)",
            "com.apurebase.kgraphql.GraphQLSchemaKt$ufoSchema$3$1.invoke(GraphQLSchema.kt)",
            "com.apurebase.kgraphql.GraphQLSchemaKt$ufoSchema$3$1.invoke(GraphQLSchema.kt)",
            "com.apurebase.kgraphql.schema.model.FunctionWrapper$ArityZero.invoke(FunctionWrapper.kt:159)",
            "com.apurebase.kgraphql.schema.model.BaseOperationDef.invoke(BaseOperationDef.kt)",
            "com.apurebase.kgraphql.schema.structure.Field$Function.invoke(Field.kt)",
            "com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.invoke$kgraphql(ParallelRequestExecutor.kt:342)",
            "com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.writeOperation(ParallelRequestExecutor.kt:74)",
            "com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.access$writeOperation(ParallelRequestExecutor.kt:25)",
            "com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$suspendExecute$2$resultMap$1.invokeSuspend(ParallelRequestExecutor.kt:54)",
            "com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$suspendExecute$2$resultMap$1.invoke(ParallelRequestExecutor.kt)",
            "com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$suspendExecute$2$resultMap$1.invoke(ParallelRequestExecutor.kt)",
            "com.apurebase.kgraphql.ExtensionsKt$toMapAsync$2$jobs$1$1.invokeSuspend(Extensions.kt:46)",
            "kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)",
            "kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)",
            "kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)",
            "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)",
            "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)",
            "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)"
          ]
        }
      }
    }
  ]
}

Change 3: Moved serialize (), which was defined as an extension function of GraphQLError, into GraphQLError. option

I want to move serialize () to use GraphQLError outside the KGraphQL plugin.
For example, KGraphQL relies on an external plugin for authentication processing.
In that case, if an authentication error occurs, 401 will occur and you will not be able to return a response in the general GraphQL error format.

Therefore, I would like to do the following.
In the StatusPages plugin, I want to create a GraphQLError and use the serialize () to make it json and respond.
For that, serialize () wants to go inside GraphQLError.

install(StatusPages) {
    status(HttpStatusCode.Unauthorized) {
        val ex =  GraphQLError("Unauthorized","AUTHORIZATION_ERROR")
        call.respond(ex.serialize())
    }
}

FFN added 6 commits December 3, 2021 23:12
…ng, Any?>?

2. Added "error type" to  make it easy to branch by error types at the client side.
3. Added debug mode. If true, exception information will be output in extensions when an error occurs.
4. Embed serialize function to GraphQLError class because we want to use GraphQLError individually. ex: at StatusPages Plugin.
Change 2: Added debug option to GraphQL Configuration (flag to output exception information to extensions)
Change 3: Moved serialize (), which was defined as an extension of GraphQLError, into GraphQLError. option
Change 2: Added debug option to GraphQL Configuration (flag to output exception information to extensions)
Change 3: Moved serialize (), which was defined as an extension of GraphQLError, into GraphQLError. option
Change 2: Added debug option to GraphQL Configuration (flag to output exception information to extensions)
Change 3: Moved serialize (), which was defined as an extension of GraphQLError, into GraphQLError. option
@Nexcius
Copy link

Nexcius commented Jan 23, 2022

This would be a very welcome change!

@MaaxGr
Copy link

MaaxGr commented Aug 7, 2022

Hey @ima9dan. I also implemented extensions in #166, but your solutions seems more advanced, because my solution don't support other types than string for extension values. I will close my merge request and point to that one! :)

@jeggy requested a unit test to prove that the solution works. That probably would also make sense here.

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

Successfully merging this pull request may close these issues.

None yet

3 participants