quarkiverse / quarkus-resteasy-problem

Unified error responses for Quarkus REST APIs via Problem Details for HTTP APIs (RFC9457 & RFC7807)
https://docs.quarkiverse.io/quarkus-resteasy-problem/dev
Apache License 2.0
65 stars 12 forks source link
exception-handling quarkus-extension rest-problem resteasy rfc7807 rfc9457

Problem Details for HTTP APIs (RFC-7807) implementation for Quarkus / RESTeasy.

License

Build status Build status Build status

RFC7807 Problem extension for Quarkus RESTeasy/JaxRS applications. It maps Exceptions to application/problem+json HTTP responses. Inspired by Zalando Problem library, originally open sourced by Tietoevry, now part of Quarkiverse.

This extension supports:

Why you should use this extension?

See Built-in Exception Mappers Wiki for more details.

From RFC7807:

HTTP [RFC7230] status codes are sometimes not sufficient to convey
enough information about an error to be helpful.  While humans behind
Web browsers can be informed about the nature of the problem with an
HTML [W3C.REC-html5-20141028] response body, non-human consumers of
so-called "HTTP APIs" are usually not.

Usage

Quarkus 3.14

Add this to your pom.xml:

<dependency>
    <groupId>io.quarkiverse.resteasy-problem</groupId>
    <artifactId>quarkus-resteasy-problem</artifactId>
    <version>3.14.0</version>
</dependency>
Quarkus up to 3.13 / Java 17+ ### Quarkus 3.X Quarkus | Java | quarkus-resteasy-problem ---------------------|------|------------------------- < 3.7.0 | 11+ | 3.1.0 \>= 3.7.0 && < 3.9.0 | 17+ | 3.7.0 \>= 3.9.0 | 17+ | 3.9.0 Make sure proper version of JDK (look for the table above), then run: ```shell mvn io.quarkus:quarkus-maven-plugin:${quarkus.version}:create \ -DprojectGroupId=problem \ -DprojectArtifactId=quarkus-resteasy-problem-playground \ -DclassName="problem.HelloResource" \ -Dpath="/hello" \ -Dextensions="resteasy,resteasy-jackson" cd quarkus-resteasy-problem-playground ./mvnw quarkus:add-extension -Dextensions="com.tietoevry.quarkus:quarkus-resteasy-problem:3.9.0" ``` Or add the following dependency to `pom.xml` in existing project: ```xml com.tietoevry.quarkus quarkus-resteasy-problem 3.9.0 ```
Quarkus 2.X / Java 11+ Make sure JDK 11 is in your PATH, then run: ```shell mvn io.quarkus:quarkus-maven-plugin:2.16.10.Final:create \ -DprojectGroupId=problem \ -DprojectArtifactId=quarkus-resteasy-problem-playground \ -DclassName="problem.HelloResource" \ -Dpath="/hello" \ -Dextensions="resteasy,resteasy-jackson" cd quarkus-resteasy-problem-playground ./mvnw quarkus:add-extension -Dextensions="com.tietoevry.quarkus:quarkus-resteasy-problem:2.2.0 ``` Or add the following dependency to `pom.xml` in existing project: ```xml com.tietoevry.quarkus quarkus-resteasy-problem 2.2.0 ```
Quarkus 1.X / Java 1.8+ Create a new Quarkus project with the following command: ```shell mvn io.quarkus:quarkus-maven-plugin:1.13.7.Final:create \ -DprojectGroupId=problem \ -DprojectArtifactId=quarkus-resteasy-problem-playground \ -DclassName="problem.HelloResource" \ -Dpath="/hello" \ -Dextensions="resteasy,resteasy-jackson,com.tietoevry.quarkus:quarkus-resteasy-problem:1.0.0" cd quarkus-resteasy-problem-playground ``` Or add the following dependency to `pom.xml` in existing project: ```xml com.tietoevry.quarkus quarkus-resteasy-problem 1.0.0 ```

Hint: you can also use resteasy-jsonb or reactive equivalents: rest-jackson / rest-jsonb instead of resteasy-jackson

Once you run Quarkus: ./mvnw compile quarkus:dev, and you will find resteasy-problem in the logs:

Installed features: [cdi, resteasy, resteasy-jackson, resteasy-problem]

Now you can throw HttpProblems (using builder or a subclass), JaxRS exceptions (e.g NotFoundException) or ThrowableProblems from Zalando library:

package problem;

import io.quarkiverse.resteasy.problem.HttpProblem;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

@Path("/hello")
public class HelloResource {

    @GET
    public String hello() {
        throw new HelloProblem("rfc7807-by-example");
    }

    static class HelloProblem extends HttpProblem {
        HelloProblem(String message) {
            super(builder()
                    .withTitle("Bad hello request")
                    .withStatus(Response.Status.BAD_REQUEST)
                    .withDetail(message)
                    .withHeader("X-RFC7807-Message", message)
                    .with("hello", "world"));
        }
    }
}

Open http://localhost:8080/hello in your browser, and you should see this response:

HTTP/1.1 400 Bad Request
X-RFC7807-Message: rfc7807-by-example
Content-Type: application/problem+json

{
    "status": 400,
    "title": "Bad hello request",
    "detail": "rfc7807-by-example",
    "instance": "/hello",
    "hello": "world"
}

This extension will also produce the following log message:

10:53:48 INFO [http-problem] (executor-thread-1) status=400, title="Bad hello request", detail="rfc7807-by-example"

Exceptions transformed into http 500s (aka server errors) will be logged as ERROR, including full stacktrace.

You may also want to check this article on RFC7807 practical usage.
More on throwing problems: zalando/problem usage

Configuration options

{ "status": 422, "title": "Constraint violation", (...) }


- (Build time) Enable Smallrye (Microprofile) metrics for http error counters. Requires `quarkus-smallrye-metrics` in the classpath.

Please note that if you use `quarkus-micrometer-registry-prometheus` you don't need this feature - http error metrics will be produced regardless of this setting or presence of this extension.

quarkus.resteasy.problem.metrics.enabled=true

Result:

GET /metrics application_http_error_total{status="401"} 3.0 application_http_error_total{status="500"} 5.0


- (Runtime) Tuning logging

quarkus.log.category.http-problem.level=INFO # default: all problems are logged quarkus.log.category.http-problem.level=ERROR # only HTTP 5XX problems are logged quarkus.log.category.http-problem.level=OFF # disables all problems-related logging


## Custom ProblemPostProcessor
If you want to intercept, change or augment a mapped `HttpProblem` before it gets serialized into raw HTTP response 
body, you can create a bean extending `ProblemPostProcessor`, and override `apply` method.

Example:
```java
@ApplicationScoped
class CustomPostProcessor implements ProblemPostProcessor {

    @Inject // acts like normal bean, DI works fine etc
    Validator validator;

    @Override
    public HttpProblem apply(HttpProblem problem, ProblemContext context) {
        return HttpProblem.builder(problem)
                .with("injected_from_custom_post_processor", "hello world " + context.path)
                .build();
    }

}

Troubles?

If you have questions, concerns, bug reports, etc, please file an issue in this repository's Issue Tracker. You may also want to have a look at troubleshooting FAQ.

Contributing

To contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For more details check the contribution guidelines.