Search code examples
javaspringspring-bootgraphqlgraphql-java

How to customize GraphQL query validation error message


I'm implementing query layer on database by using GraphQl and spring boot project to perform CRUD operation on sql database. In GraphQL schema i mentioned some fields to be mandatory and when those fields are not mentioned in query it is returning ValidationError error message in default format with 200 status code.

Error :

{
"data": null,
"errors": [
    {
         value=StringValue{value='1235'}}]}}]}' is missing required fields '[book_type]' @ 'create_book'",
        "locations": [
            {
                "line": 3,
                "column": 23,
                "sourceName": null
            }
        ],
        "description": "argument 'insert' with value value=StringValue{value='1235'}}]}}]}' is missing required fields '[book_type]'",
        "validationErrorType": "WrongType",
        "queryPath": [
            "create_book"
        ],
        "errorType": "ValidationError",
        "path": null,
        "extensions": null
    }
],
"dataPresent": false,
"extensions": null
}

And here is my code with layer architecture pattern

Controller :

@Autowired
private GraphQLServer graphQlServer;

@PostMapping("test")
public ResponseEntity<Object> graphQl(@RequestBody String body){
    ExecutionResult response = graphQlServer.execute(body);

    return ResponseEntity.ok(response);
}

Service :

@Service
public class GraphQLServer {

@Autowired
private GraphQL graphQl;

public ExecutionResult execute(String query) {
    return graphQl.execute(query);
   }

}

Config :

@Bean
public GraphQL loadSchema() throws IOException {

    File schemaFile = schemaResource.getFile();

    TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaFile);
    RuntimeWiring wiring = buildRuntimeWiring();
    GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
    return GraphQL.newGraphQL(schema).build();
}

private RuntimeWiring buildRuntimeWiring() {

    return RuntimeWiring.newRuntimeWiring()
            .type("Mutation", mutationWiring -> mutationWiring.dataFetcher("create_book", bookDataFetcher))
            .build();

}

BookDataFetcher :

@Override
public Map<String, Object> get(DataFetchingEnvironment environment) {

    //return data from db by getting Map properties from environment

}

The above code is working as expected but my question here is How to customize the error message? In the error message i would like to mention the status 400 since it is bad request from client


Solution

  • First of all , you should call toSpecification() on ExecutionResult to make sure the response obeys the GraphQL Specification.

    By default , there is only one ExecutionResult 's implementation provided by which is ExecutionResultImpl , so you can cast ExecutionResult to it in order to use its transform() to update its state.

    ExecutionResultImpl internally contains all errors detected by the . All of them are in the subclass of GraphQLError which mean you have to cast it to the specific sub-class during customization.

    In your case , the subclass is ValidationError and the codes look something like :

    @PostMapping("test")
    public ResponseEntity<Object> graphQl(@RequestBody String body){
    
        ExecutionResult response = graphQlServer.execute(body);
    
        ExecutionResultImpl responseImpl = (ExecutionResultImpl) response;
    
        List<GraphQLError> customizedErrors = Lists.newArrayList();
        for (GraphQLError gqlError : responseImpl.getErrors()) {
                //Do your error custmosation here....
                GraphQLError customizedError = gqlError;
                if (gqlError instanceof ValidationError) {
                    ValidationError error = (ValidationError) gqlError;
                    customizedError = new ValidationError(error.getValidationErrorType(), error.getLocations(),
                            "Customizing some error message blablabla....");
                }
                customizedErrors.add(customizedError);
        }
    
        Map<String, Object> specResponse = responseImpl.transform(b->b.errors(customizedErrors)).toSpecification();
    
        return ResponseEntity.ok(specResponse);
    }