Coder Social home page Coder Social logo

quarkiverse / quarkus-resteasy-problem Goto Github PK

View Code? Open in Web Editor NEW
64.0 5.0 12.0 789 KB

Unified error responses for Quarkus REST APIs via Problem Details for HTTP APIs (RFC9457 & RFC7807)

Home Page: https://docs.quarkiverse.io/quarkus-resteasy-problem/dev

License: Apache License 2.0

Java 99.16% Shell 0.84%
resteasy rfc7807 quarkus-extension exception-handling rfc9457 rest-problem

quarkus-resteasy-problem's Introduction

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

Release Quarkus Quarkus

Release Quarkus Quarkus

Release Quarkus Quarkus

License

Build status Build status Build status Compatibility with latest stable Quarkus

RFC7807 Problem extension for Quarkus RESTeasy/JaxRS applications. It maps Exceptions to application/problem+json HTTP responses. Inspired by Zalando Problem library.

This extension supports:

  • Quarkus 1, 2 and 3
  • quarkus-resteasy-jackson and quarkus-resteasy-jsonb
  • quarkus-rest-jackson and quarkus-rest-jsonb
  • JVM and native mode

Why you should use this extension?

  • consistency - it unifies your REST API error messages, and gives it much needed consistency, no matter which JSON provider (Jackson vs JsonB) or paradigm (classic/blocking vs reactive) you're using.

  • predictability - no matter what kind of exception is thrown: expected (thrown by you on purpose), or unexpected (not thrown 'by design') - your API consumer gets similar, repeatable experience.

  • safety - it helps prevent leakage of some implementation details like stack-traces, DTO/resource class names etc.

  • time-saving - in most cases you will not have to implement your own JaxRS ExceptionMappers anymore, which makes your app smaller, and less error-prone.

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.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:

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:

<dependency>
    <groupId>com.tietoevry.quarkus</groupId>
    <artifactId>quarkus-resteasy-problem</artifactId>
    <version>3.9.0</version>
</dependency>
Quarkus 2.X / Java 11+

Make sure JDK 11 is in your PATH, then run:

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:

<dependency>
    <groupId>com.tietoevry.quarkus</groupId>
    <artifactId>quarkus-resteasy-problem</artifactId>
    <version>2.2.0</version>
</dependency>
Quarkus 1.X / Java 1.8+

Create a new Quarkus project with the following command:

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:

<dependency>
  <groupId>com.tietoevry.quarkus</groupId>
  <artifactId>quarkus-resteasy-problem</artifactId>
  <version>1.0.0</version>
</dependency>

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 com.tietoevry.quarkus.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

  • (Build time) Include MDC properties in the API response. You have to provide those properties to MDC using MDC.put
quarkus.resteasy.problem.include-mdc-properties=uuid,application,version

Result:

{
  "status": 500,
  "title": "Internal Server Error",
  "uuid": "d79f8cfa-ef5b-4501-a2c4-8f537c08ec0c",
  "application": "awesome-microservice",
  "version": "1.0"
}
  • (Runtime) Changes default 400 Bad request response status when ConstraintViolationException is thrown (e.g. by Hibernate Validator)
quarkus.resteasy.problem.constraint-violation.status=422
quarkus.resteasy.problem.constraint-violation.title=Constraint violation

Result:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
    "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:

@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.

quarkus-resteasy-problem's People

Contributors

dependabot[bot] avatar lwitkowski avatar amasnik avatar pazkooda avatar chberger avatar jburkal avatar

Stargazers

Pedro Hos avatar Melloware avatar User avatar tuyucheng avatar Loi Nguyen avatar Jonas Bendeguz Gergo avatar Akhilesh Kataria avatar Moncef AOUDIA avatar Burak Dogruoz avatar Taketoday avatar Thore Johnsen avatar Markus Bachmann avatar Eric Lafargue avatar Eliezio Oliveira avatar Michael Sonnleitner avatar Tien Bui avatar Leo avatar Hantsy Bai avatar Gaëtan Bloch avatar Ahmed Metwally avatar Bjørn T Johansen avatar  avatar Arthur H. Eggert avatar rodolfodpk avatar Stephan Strate avatar Kevin Wooten avatar Cedric Thiebault avatar 阿男 avatar Georgios Andrianakis avatar Rodrigo Casara avatar Sergei Legkodymov avatar  avatar  avatar  avatar  avatar  avatar Claudio Altamura avatar  avatar  avatar Alexander Openkowski avatar Panos L avatar Sander avatar Thomas Bredzinski avatar  avatar Mosaab Abbas avatar  avatar Jonatan Pedro da Silva avatar Sebastien FAUVART avatar Dominik Boller avatar Djalma avatar  avatar s avatar Gavin Ray avatar Johannes Wienke avatar Ivan Vilanculo avatar Włodzimierz Kozłowski avatar  avatar ismail BASKIN avatar Kristjan Hendrik Küngas avatar Simen Flatby avatar Daniel PETISME avatar Martin Andreas Ullrich avatar Paweł Ochrymowicz avatar  avatar

Watchers

Akhilesh Kataria avatar Endre Karlson avatar James Cloos avatar  avatar  avatar

quarkus-resteasy-problem's Issues

Missing `WWW-Authenticate` header when `UnauthorizedException` or `AuthenticationFailedException` is thrown

Describe the bug
From HTTP RFC:

 A server generating a 401 (Unauthorized) response MUST send a
 WWW-Authenticate header field containing at least one challenge.  A
 server MAY generate a WWW-Authenticate header field in other response
 messages to indicate that supplying credentials (or different
 credentials) might affect the response.

Default Quarkus response for these exceptions contains WWW-Authenticate header:

HTTP/1.1 401 Unauthorized
www-authenticate: Bearer {token}
Content-Length: 0

To Reproduce
Call endpoint that requires valid JWT token.

Current response:

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json

{
  "status": 401,
  "title": "Unauthorized",
  "instance": "/resource"
}

Expected behavior
Response should include WWW-Authenticate header with proper auth method (not only bearer {token}):

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer {token}
Content-Type: application/problem+json

{
  "status": 401,
  "title": "Unauthorized",
  "instance": "/resource"
}

The default for the "instance" field violates the RFC

ProblemDefaultsProvider replaces null value of instance with URI of currently served endpoint, i.e /products/123
which violates the rfc7807/rfc9457:

The "instance" member is a JSON string containing a URI reference that identifies the specific occurrence of the problem.

So it isn't supposed to be an URI of the currently served endpoint but each occurrence of the problem should have a unique identifier.

Introduced by
#39

To Reproduce
Steps to reproduce the behavior:

  • Pretty much any request which results into a problem response which doesn't explicitly provide the "instance" field.

Expected behavior
"instance" field not present or has a valid value. Or at least ProblemDefaultsProvider can be disabled (#326)

Workaround

@ApplicationScoped
public class FixProblemInstanceProcessor implements ProblemPostProcessor {

    /**
     * Has to run after com.tietoevry.quarkus.resteasy.problem.postprocessing.ProblemDefaultsProvider. See {@link ProblemDefaultsProvider#priority()}
     */
    @Override
    public int priority() {
        return 99;
    }

    @Override
    public HttpProblem apply(HttpProblem problem, ProblemContext context) {
        return HttpProblem.builder(problem)
                .withInstance(null) //or a valid value
                .build();
    }
}

Not all Jackson exceptions mapper have been overruled

Quarkus: 3.4.3
Resteasy-Problem: 3.0.0

It seems like that not all Jackson exceptions are handled by resteasy-problem.

image

Although there is a mapper for JsonProcessingException, both child exceptions MismatchedInputException and InvalidDefinitionException are handled by Jackson itself.

According to the spec, this seems totally fine:

When choosing an exception mapping provider to map an exception, an implementation MUST use the
provider whose generic type is the nearest superclass of the exception. If two or more exception providers
are applicable, the one with the highest priority MUST be chosen as described in Section 4.1.3.

That is a little annoying cause it looks like you need to overrule each exception mapper individually.

Do you have any strategy in place to detected new mappers automatically?

An opt-in or opt-out mechanism for default ProblemPostProcessors

Describe the solution you'd like
It would be nice if a user could opt-in or opt-out for the default ProblemPostProcessors. Currently, they are registered (by intention) somehow hard-coded in the PostProcessorsRegistry:

    public synchronized void reset() {
        processors.clear();
        register(new ProblemLogger(LoggerFactory.getLogger("http-problem")));
        register(new ProblemDefaultsProvider());
    }

In addition, in comparison to the custom PostProblemProcessors, these beans are just Pojos and no managed beans. Thus a replacement/adjustment of the functionality is not that easy. In our case we'd like to change the implementation of ProblemLogger. Instead of slf4j we'd like to use jboss-loggingand we would also log some additional fields (trace & span ids) beside the serialized http problem and context data.

Right now we use ASM to modify the bytecode to get rid of the registration. However, this approach isn't that maintainable and I would love to have a configuration option in place.

@lwitkowski wdyt?

Throwing problems with http headers

Is your feature request related to RFC7807? Please describe.
Zalando library does not provide an easy way to define custom headers when throwing ThrowableProblem - problem itself describes only response body. This becomes a challenge, when developer wants to have both - application/problem+json and custom headers (e.g for http 429 + Retry After header).

One possible walk around is to throw WebApplicationException with Response where we can set headers and provide Problem as entity/body:

throw new WebApplicationException(
    Response
        .status(Response.Status.BAD_REQUEST)
        .header("X-Special-Header", "ABC")
        .type("application/problem+json")
        .entity(
            Problem.builder()
                .withStatus(Status.BAD_REQUEST)
                .withTitle("Problem title")
                .with("custom_field", "CustomValue")
                .build()
            )
        .build()
);

This works pretty well, but such WebApplicationException will skip all ExceptionMappers and our PostProcessors, so it will not be enhanced (instance default value, mdc fields injection) nor logged, so does not provide consistent developer experience.

Describe the solution you'd like
Provide new abstraction HttpProblem + nice builder + ExceptionMapper that will handle this type properly. Possible usage:

throw HttpProblem.builder()
    .withStatus(Status.BAD_REQUEST)
    .withTitle("Problem title")
    .withHeader("X-Special-Header", "ABC")
    .with("custom_field", "CustomValue")
    .build();

Zalando's ProblemBuilder class is final, so we have to create our own copy with headers functionality.

This should produce exactly the same response than WebApplicationException in the example above, but transformed by PostProcessors (mdc fields, logging etc):

HTTP/1.1 400 Bad Request
X-Special-Header: ABC
Content-Type: application/problem+json
        
{
  "title": "Problem title",
  "status": 400,
  "custom_field": "CustomValue",
  "mdc_field": "123"
}

This could also be a step towards getting rid of Zalando lib dependency.

Throwing `ConstraintViolationException` with null as `constraintViolations` results in NPE in exception mapper

Describe the bug
Throwing ConstraintViolationException with null as constraintViolations results in NPE in exception mapper.

To Reproduce
When:

throw new ConstraintViolationException(message, null);

Then:

ERROR: HTTP Request to /some-endpoint failed, error id: 06220342-0a3f-47e1-ac62-1aedde3ccd8e-1
java.lang.NullPointerException: Cannot invoke "java.util.Set.stream()" because the return value of "javax.validation.ConstraintViolationException.getConstraintViolations()" is null
	at com.tietoevry.quarkus.resteasy.problem.validation.ConstraintViolationExceptionMapper.toProblem(ConstraintViolationExceptionMapper.java:39)
	at com.tietoevry.quarkus.resteasy.problem.validation.ConstraintViolationExceptionMapper.toProblem(ConstraintViolationExceptionMapper.java:30)
	at com.tietoevry.quarkus.resteasy.problem.ExceptionMapperBase.toResponse(ExceptionMapperBase.java:24)
	at ...

Expected behavior
ConstraintViolationExceptionMapper should gracefully handle null set of constraintViolations, as this constructor argument is annotated and documented as nullable.

Add mapper for `io.quarkus.rest.data.panache.RestDataPanacheException`

Not sure then this exception is thrown exactly (to be investigated as part of this task), but there are multiple custom exception mappers:

io.quarkus.spring.data.rest.runtime.RestDataPanacheExceptionMapper implements ExceptionMapper<RestDataPanacheException>
io.quarkus.hibernate.orm.rest.data.panache.runtime.RestDataPanacheExceptionMapper implements ExceptionMapper<RestDataPanacheException>
io.quarkus.mongodb.rest.data.panache.runtime.RestDataPanacheExceptionMapper implements ExceptionMapper<RestDataPanacheException>

which we should override (ours should have higher priority than any of the above) if panache (or one of related extensions) is in classpath, and turn this exception into nice problem.

NullpointerException WebApplicationException when Response Headers not set

When mapping a WebApplicationException whose response returns null for getHeaders, an NPE is thrown and the original exception informaiton is lost.

To Reproduce

  • Quarkus version: 3.5.2
  • quarkus-resteasy-problem 3.1.0

Use a rest client that returns null headers instead of an empty map (in my case, keycloak admin client).

    @Path("reproduce")
    @GET
    public RealmRepresentation reproduce() {
        return KeycloakBuilder.builder()
                .serverUrl("http://keycloak.obsidian.com")
                .clientId("admin-cli")
                .realm("404")
                .username("user")
                .password("password")
                .build()
                .realm("404")
                .toRepresentation();
    }
{
    "details": "Error id 44fbd7fa-b5c8-4c5e-87bf-91492cbd4d12-4, java.lang.NullPointerException: Cannot invoke \"jakarta.ws.rs.core.MultivaluedMap.forEach(java.util.function.BiConsumer)\" because the return value of \"jakarta.ws.rs.core.Response.getHeaders()\" is null",
    "stack": "java.lang.NullPointerException: Cannot invoke \"jakarta.ws.rs.core.MultivaluedMap.forEach(java.util.function.BiConsumer)\" because the return value of \"jakarta.ws.rs.core.Response.getHeaders()\" is null\n\tat com.tietoevry.quarkus.resteasy.problem.jaxrs.WebApplicationExceptionMapper.toProblem(WebApplicationExceptionMapper.java:28)\n\tat com.tietoevry.quarkus.resteasy.problem.jaxrs.WebApplicationExceptionMapper.toProblem(WebApplicationExceptionMapper.java:13)\n\tat com.tietoevry.quarkus.resteasy.problem.ExceptionMapperBase.toResponse(ExceptionMapperBase.java:23)\n\tat org.jboss.resteasy.reactive.server.core.RuntimeExceptionMapper.mapException(RuntimeExceptionMapper.java:100)\n\tat org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext.mapExceptionIfPresent(ResteasyReactiveRequestContext.java:337)\n\tat org.jboss.resteasy.reactive.server.handlers.ExceptionHandler.handle(ExceptionHandler.java:15)\n\tat io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:150)\n\tat org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)\n\tat io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)\n\tat org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)\n\tat org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)\n\tat org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)\n\tat org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)\n\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\n\tat java.base/java.lang.Thread.run(Thread.java:840)"
}

Expected behavior
Null headers should be handled gracefully:

{
    "status": 404,
    "title": "Not Found",
    "detail": "Received: 'Server response is: 404' when invoking: Rest Client method: 'org.keycloak.admin.client.token.TokenService#grantToken'",
    "instance": "/reproduce"
}

Improve dev experience of creating custom ProblemPostProcessor

I want to implement a ProblemPostProcessor and need access to the original exception. The ProblemContext is passed to the apply-Method, but it's fields, including cause, is package scoped.

It would be nice to provide getters for the fields in the ProblemContext class.

`HttpAuthenticator` is ignored when authentication fails

Describe the bug
In some setups (e.g oidc) when authentication check fails and throws UnauthorizedException or AuthenticationFailedException then exception mapper should not blindly return http 401, it should look at HttpAuthenticator whether there's a ChallengeData provided by custom auth handlers (like .oidc) and use it to build the response.

To Reproduce
TODO

Expected behavior
Classic: https://github.com/quarkusio/quarkus/blob/main/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/UnauthorizedExceptionMapper.java#L42
Reactive: https://github.com/quarkusio/quarkus/blob/main/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/exceptionmappers/SecurityExceptionMapperUtil.java

Ref
Probably closes #108
jhipster/generator-jhipster-quarkus#248

Issue regarding propertynames in constraintviolation.

When I throw a plain constraing violation, violating as an example: email address.
I recieve the message in the issue, but I do not get the fieldname or what the actual value is.

I reviewed the code and found the below implementation quite weird.

Why do we visit the iterator twice to get the propertyname?

https://github.com/TietoEVRY/quarkus-resteasy-problem/blob/da642a0aad2d5892fc1ecc466edf4005c468e8db/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/validation/ConstraintViolationExceptionMapper.java#L64-L78

Add `instance` member default value

We do not use these Problem members as of today (unless client throws ThrowableProblem with those fields provided), but they are a vital part of the standard:
https://tools.ietf.org/html/rfc7807#page-5
https://github.com/zalando/problem
Ref type: https://github.com/zalando/problem-spring-web/blob/main/problem-violations/src/main/java/org/zalando/problem/violations/ConstraintViolationProblem.java#L19
Ref instance: https://github.com/cloudstark/quarkus-zalando-problem-extension#define-jax-rs-resource

For built-in exceptions (not ThrowableProblems) we could use the following rules:
type could be URI made out of http status phrase, i.e for http 400 it would be /bad-request
instance could be simply URI of currently served endpoint, i.e /products/123

For ThrowableProblems explicit values from given object would be taken, with a fallback to described above rules (or to empty values, to be decided)

Resteasy Problem might adhere to RFC 9457 (Problem Details for HTTP APIs)

Is your feature request related to RFC7807? Please describe.

The positive experience of RFC 7807, whose journey began in 2016, is concluded (deprecation) but also confirmed with a new official proposition: the RFC 9457 (published July 2023).

RFC 7807 is pretty comprehensive and has served already for some time, but RFC 9457 brings some features that represent a positive evolution of best practice. However, the changes between the two RFC versions are very small; helpfully the RFC itself includes a section outlining the changes.

The key items here are around representing multiple problems, providing guidance for using type URIs that cannot be dereferenced and using a shared registry of problem types.

Describe the solution you'd like

Especially the section 3 clarifies how multiple problems should be treated. So they have a strong opinion on how to design an error response returning more than one instance of the same problem type. For instance:

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
Content-Language: en

{
 "type": "https://example.net/validation-error",
 "title": "Your request is not valid.",
 "errors": [
             {
               "detail": "must be a positive integer",
               "pointer": "#/age"
             },
             {
               "detail": "must be 'green', 'red' or 'blue'",
               "pointer": "#/profile/color"
             }
          ]
}

The problem type here defines the "errors" extension, an array that describes the details of each validation error. Each member is an object containing "detail" to describe the issue and "pointer" to locate the problem within the request's content using a JSON Pointer [JSON-POINTER].

While having #313 and #314 ready, the implementation of com.tietoevry.quarkus.resteasy.problem.validation.ConstraintViolationExceptionMapper could be easily adapted to comply with RFC9457. Maybe this would be a first good candidate to evolve with the spec changes/enhancements.

Http response does not include custom headers

Reproduce steps:

  1. Throw new WebApplicationException with custom header i.e:
throw new WebApplicationException(Response.status(Response.Status.TOO_MANY_REQUESTS)
                    .header(HttpHeaders.RETRY_AFTER, "Try again later")
                    .build());
  1. Custom headers are missing in response
    too_many

Stacktrace not printing out when enum fails to parse

Maybe I'm missing a setting but when I send in an invalid enum into my REST endpoint, I get the 400 Bad Request as expected.
However, I don't get any other information about WHY it was a bad requesst in my logs.

I just get that it was a 400 bad request.

Is there a way to get the stack trace to print out on WHAT caused the bad request?

Thanks!

Closer integration with Quarkus Core/Quarkiverse/Quarkus Platform

What do we want to achieve?

  1. We want this extension to be listed on https://code.quarkus.io/
  2. ./mvnw quarkus:add-extension -Dextensions="quarkus-resteasy-problem" should just work
  3. We don't want end users to be forced to specify (and bump) this extension's version in their pom.xml - we want to be a part of quarkus-universe-bom

How to get there?

https://quarkus.io/blog/quarkiverse/ - this requires changing group/artefact id and migrate project to quarkiverse github org

Quarkus Platform - some 3rd party extensions are part of quarkus universe bom, but are not in quarkiverse though: https://github.com/datastax/cassandra-quarkus

3rd option is to become integral part of Quarkus core, there's ongoing discussion on dev mailing list

ValidationException should not result in HTTP 400 status

An exception of class javax/jakarta.validation.ValidationException is handled by the ValidationExceptionMapper, which maps it to an HTTP 400 status and doesn't log the exception.

However, ValidationException and its subclasses (except ConstraintViolationException) are not thrown due to failed validation, but are instead thrown on invalid use of the framework (e.g. unexpected exceptions thrown from validators).

This can clearly be seen from the names of the sub-classes:

  • ConstraintDeclarationException
  • ConstraintDefinitionException
  • NoProviderFoundException
  • ValueExtractorDefinitionException

The exception mapper in Quarkus is also quite clear about this exception not being thrown as a result of failed validation:

// Not a violation in a REST endpoint call, but rather in an internal component.
// This is an internal error: handle through the QuarkusErrorHandler,
// which will return HTTP status 500 and log the exception.

I believe it would be more correct for these exceptions to result in an HTTP 500 status being returned, and the exception logged.
I'll be happy to create a pull request that fixes this.

Tested with com.tietoevry.quarkus:quarkus-resteasy-problem:2.1.0 using Quarkus 2.14.3.

Adapt Maven Central release to Token based credentials

Due to e-mail send from Maven Central we should update our credentials to be able to publish artifacts to Central Repository:

from: The Central Team [email protected]
reply-to: [email protected]
to: ...
date: 11 Jun 2024, 18:00
subject: Maven Central account migration

Dear Maven Central publisher,

We are making changes to the OSSRH authentication backend. For most users this should be a transparent process, and you should be able to continue to use your existing username and password to connect the Nexus UI. In case you need to update your password, please follow our documentation.

To configure a publisher’s plugin authentication you would need to update your plugin settings to use a user token instead of the Nexus UI username and password login.

For more information about publishing to legacy OSSRH please consult our documentation at https://central.sonatype.org/register/legacy/

Thank you,
The Central Team

If I correctly understood simple change of secrets.NEXUS_PASSWORD to secrets.NEXUS_TOKEN (to be generated and set in GH) in this line: https://github.com/TietoEVRY/quarkus-resteasy-problem/blob/358f9a7491b27bec4908b6db0d1cb577b8614c8c/.github/workflows/publish.yml#L24 should do the job.

instance field may contain data that is not a URI

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  • Construct a new Quarkus project with the steps indicated in the project README
  • mvn quarkus:dev
  • curl http://localhost:8080/unknown

Expected behavior

The contents of the returned instance field are a valid URI conforming to https://www.rfc-editor.org/rfc/rfc3986#section-3 This is what is referred to from the problem+json RFC https://datatracker.ietf.org/doc/html/rfc7807#section-3.1.

Right now, the following JSON is returned:

{
  "status": 404,
  "title": "Not Found",
  "detail": "RESTEASY003210: Could not find resource for full path: http://localhost:8080/unknown",
  "instance": "/unknown"
}

RFC 3986 always requires a scheme:

The scheme and path components are required, though the path may be empty.

Therefore, instances should be prefixed with http....

Failed validation of query param returns empty `field` in `violations` list

Describe the bug
Failed validation of primitive input returns empty 'field' in violations list

To Reproduce
Endpoint:

@GET
@Path("/search")
public String search(@QueryParam("phrase") @Length(min = 10) String phrase) {
    return "not relevant";
}

Request:

curl http://localhost:8080/search?phrase=abc

Response:

{
    (...)
    "violations": [
        {
            "field": "",
            "message": "length must be between 10 and 2147483647"
        }
    ]
}

Expected behavior
field in violation object should be phrase instead of empty string

{
    (...)
    "violations": [
        {
            "field": "phrase",
            "message": "length must be between 10 and 2147483647"
        }
    ]
}

JsonB Problem serializer not registered, when client application provides custom JsonbConfig

Reproducer (code in client application):

@Provider
public class JsonbConfiguration implements ContextResolver<Jsonb> {
    @Override
    public Jsonb getContext(Class<?> type) {
        return JsonbBuilder.create(new JsonbConfig());
    }
}

Then JsonBProblemSerializer from this extension is not registered, and all internal fields from Problem object are revealed (like stackTrace etc), which is not a good thing.

Vanilla resteasy-jsonb application doesn't start

Describe the bug
Apprently this extension has hidden dependencies to jackson, which causes app to crash while booting up if only quarkus-resteasy-jsonb is present.

To Reproduce

mvn io.quarkus:quarkus-maven-plugin:1.13.2.Final:create \
    -DprojectGroupId=problem \
    -DprojectArtifactId=quarkus-resteasy-problem-playground \
    -DclassName="problem.HelloResource" \
    -Dpath="/hello" \
    -Dextensions="resteasy,resteasy-jsonb"
cd quarkus-resteasy-problem-playground
// add quarkus-resteasy-problem:0.9.5 to pom.xml
./mvnw clean quarkus:dev

App will not start properly, error stack trace in the logs:

Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.ObjectMapper

Expected behavior
App should start without errors.

Scope for this extension?

Which exceptions should we handle in our extension and which should be left for application developers?
Ref #85 #84

  • all exception mappers from quarkus core? <- @lwitkowski: my preferred strategy
  • a minimal subset of the quarkus core exception mappers?
  • quarkus core + some most popular quarkiverse extensions exceptions?
  • All exceptions from JaxRs specification?
  • What about resteasy specific exceptions (ref #46) ?
  • Should we do something for client side (deserialization and throwing Problems from RestClients)

Support for RESTeasy reactive

Does this extension work with RESTeasy reactive? What are the needed changes to make it compatible? Which tests we have to add?

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>

Fails with Quarkus `2.12.0.CR1` due to `ProblemProcessor#warnOnMissingSmallryeMetricsDependency`

Describe the bug

2022-08-17 15:46:26,104 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus:
java.lang.RuntimeException: java.lang.IllegalArgumentException:
Build step 'com.tietoevry.quarkus.resteasy.problem.deployment.ProblemProcessor#warnOnMissingSmallryeMetricsDependency'
does not produce any build item and thus will never get executed.

Either change the return type of the method to a build item type, add a parameter of type
BuildProducer<[some build item type]>/Consumer<[some build item type]>,
or annotate the method with @Produces. Use @Produce(EmptyBuildItem.class) if you want to always execute this step.

        at io.quarkus.runner.bootstrap.AugmentActionImpl.runAugment(AugmentActionImpl.java:330)                                                                                                                                     
        at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:252)
        at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:60)                                                                                                                 
        at io.quarkus.deployment.dev.IsolatedDevModeMain.firstStart(IsolatedDevModeMain.java:87)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:448)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:60)
        at io.quarkus.bootstrap.app.CuratedApplication.runInCl(CuratedApplication.java:148)
        at io.quarkus.bootstrap.app.CuratedApplication.runInAugmentClassLoader(CuratedApplication.java:103)
        at io.quarkus.deployment.dev.DevModeMain.start(DevModeMain.java:131)
        at io.quarkus.deployment.dev.DevModeMain.main(DevModeMain.java:62)

Caused by: java.lang.IllegalArgumentException: Build step 'com.tietoevry.quarkus.resteasy.problem.deployment.ProblemProcessor#warnOnMissingSmallryeMetricsDependency' does not produce any build item and thus will never get executed.

Either change the return type of the method to a build item type, add a parameter of type BuildProducer<[some build item type]>/Consumer<[some build item type]>, or annotate the method with @Produces. Use @Produce(EmptyBuildItem.class) if you want to always execute this step.

To Reproduce
Steps to reproduce the behavior:

  • Sample application or code (github repo / gist / inline)
  • Quarkus version, quarkus-resteasy-problem version

Expected behavior
A clear and concise description of what you expected to happen.

Improve Error Handling with malformed json

Currently Jackson and Jsonb Exception Mappers return responses, which contain detailed information about the malformed request object. From a consumer point of view this makes sense and improves developer expierence.

Example:
{ "status": 400, "title": "Bad Request", "detail": "Internal error: Invalid token=STRING at (line no=3, column no=11, offset=52). Expected tokens are: [COMMA]", "instance": "/token" }

But the responses also contain implementation details, which from a security point of view is not so good. Especially if your service is a an external public API.
https://owasp.org/www-community/Improper_Error_Handling
Example:
{ "status": 400, "title": "Bad Request", "detail": "Unable to deserialize property 'token' because of: Problem adapting object of type interface org.example.entity.Token to class java.lang.String in class class org.example.enity.TokenTypeAdapter", "instance": "/token" }

Solution

What could help is an option to control the output to return a more generic response and log the detailed error message in the backend.
Example:
{ "status": 400, "title": "Bad Request", "detail": "Syntax Error: malformed json", "instance": "/token" }

Exception occurring when encoding the path containing unwise characters (i.e space)

Describe the bug
The below exception is occurring when there is a space in the path which is not encoded, like /api/v1/users/Some Space.

IllegalArgumentException: Illegal character in path at index 29: /api/v1/users/Some Space\n\tat java.base/java.net.URI.create(URI.java:906)\n\tat com.tietoevry.quarkus.resteasy.problem.postprocessing.ProblemDefaultsProvider.defaultInstance(ProblemDefaultsProvider.java:29)\n\tat com.tietoevry.quarkus.resteasy.problem.postprocessing.ProblemDefaultsProvider.apply(ProblemDefaultsProvider.java:24)\n\tat com.tietoevry.quarkus.resteasy.problem.postprocessing.PostProcessorsRegistry.applyPostProcessing(PostProcessorsRegistry.java:44)\n\tat com.tietoevry.quarkus.resteasy.problem.ExceptionMapperBase.toResponse(ExceptionMapperBase.java:25)\n\tat org.jboss.resteasy.core.ExceptionHandler.executeExceptionMapper(ExceptionHandler.java:139)\n\tat org.jboss.resteasy.core.ExceptionHandler.unwrapException(ExceptionHandler.java:183)\n\tat org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:100)\n\tat org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:344)

Looking at the code it looks like the defaultInstance method in ProblemDefaultsProvider turns the path to a URI without any encoding.
return context.path == null ? null : URI.create(context.path);

To Reproduce
Use a path which has a space in it for example /api/v1/users/Some Space

Expected behavior
An exception not to occur and the path gets encoded before it turns to a URI.

Jackson's `InvalidFormatException` produces invalid 'field' value if caused by collection item

To Reproduce
Given JaxRS endpoint:

@POST
@Path("/throw/json/")
public void endpoint(TestRequestBody body) {}

public static final class TestRequestBody {
    public List<Nested> collection;

    public static final class Nested {
        public UUID uuid_field_2;
    }
}

When sending this body:

{
    "collection": [{ "uuid_field_2": "ABC-DEF-GHI" }]
}

Current output (ugly null):

{
    (...),
    "field": "collection.null.uuid_field_2"
}

Expected behaviour
Output:

{
    ...,
    "field": "collection[0].uuid_field_2"
}

Add mapper for jackson `com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException`

Is your feature request related to RFC7807? Please describe.
Quarkus provides custom exception mapper for this exception, and client gets 'test/html' response:
org.jboss.resteasy.plugins.providers.jackson.UnrecognizedPropertyExceptionHandler

This exception is thrown when request payload json does not fit DTO object with @Valid annotation (e.g field has different name) and FAIL_ON_UNKNOWN_PROPERTIES is enabled (default changed in quarkus 1.11)

Describe the solution you'd like
UnrecognizedPropertyException should be mapped to application/problem+json.

Quickstarts are broken

Describe the bug
Quickstart builds and Usage instructions in readme stopped working with the latest/greatest quarkus-maven-plugin, I have no idea why, as I'm pretty sure it worked few days ago with quarkus 2.0.0.FInal bom

To Reproduce
Run Quickstart test in Github Actions
or
Try Usage instructions from readme

Improve validation problem responses

We see a need to improve ConstraintViolationException responses, as field values could be less cryptic for REST api client than they are now.

JaxRS endpoint example:

@POST
@Path("/resource/{param}")
public void endpoint(
        @Valid @PathParam("param") @Length(min = 100, max = 101) String pathParam,
        @Valid @QueryParam("param") @Length(min = 200, max = 201) String queryParam,
        @Valid @HeaderParam("param") @Length(min = 300, max = 301) String headerParam,
        @Valid RequestBody body) {
}

static class RequestBody {
    @Length(min = 6, max = 10)
    String param = "";

    @Valid
    List<Item> list = Collections.singletonList(new Item());

    static class Item {
        @Length(min = 7, max = 10)
        String param = "";
    }

    @Valid
    NestedObject nested = new NestedObject();

    static class NestedObject {
        @Length(min = 8, max = 10)
        String param = "";
    }
}

Current output that needs improvements (empty field for path, query and header param violations, lack of index for body list.item)

{
    "status": 400,
    "title": "Bad Request",
    "instance": "/resource/1",
    "violations": [
        {
            "field": "",
            "message": "length must be between 100 and 101"
        },
        {
            "field": "",
            "message": "length must be between 200 and 201"
        },
        {
            "field": "",
            "message": "length must be between 300 and 301"
        },
        {
            "field": "param",
            "message": "length must be between 6 and 10"
        },
        {
            "field": "list.param",
            "message": "length must be between 7 and 10"
        },
        {
            "field": "nested.param",
            "message": "length must be between 8 and 10"
        }
    ]
}

Expected output (mind better field values and new property in in violation item):

{
    "status": 400,
    "title": "Bad Request",
    "instance": "/resource/1",
    "violations": [
        {
            "field": "param",
            "in": "path",
            "message": "length must be between 100 and 101"
        },
        {
            "field": "param",
            "in": "query",
            "message": "length must be between 200 and 201"
        },
        {
            "field": "param",
            "in": "header",
            "message": "length must be between 300 and 301"
        },
        {
            "field": "param",
            "in": "body",
            "message": "length must be between 6 and 10"
        },
        {
            "field": "list[0].param",
            "in": "body",
            "message": "length must be between 7 and 10"
        },
        {
            "field": "nested.param",
            "in": "body",
            "message": "length must be between 8 and 10"
        }
    ]
}

To keep this description short @FormParam was not included, as it is mutually exclusive with json body payload, but it should be properly handled as well.

field values should also work properly if custom propertyNodeNameProvider is configured for Hibernate Validator.

ConstraintViolationException HTTP status should be configurable

Hello,

some people and companies favor the usage of HTTP-Status Code 422 Unprocessable Entity over 400 Bad Request for validation errors.

I believe it would be convient, if devs could configure the Response Status Code for ConstraintViolationExceptions by a property.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.