Coder Social home page Coder Social logo

zalando / problem-spring-web Goto Github PK

View Code? Open in Web Editor NEW
1.0K 53.0 125.0 1.51 MB

A library for handling Problems in Spring Web MVC

License: MIT License

Java 99.70% Shell 0.30%
java json exception error problem rfc7807 spring spring-boot web microservice

problem-spring-web's Introduction

Problems for Spring MVC and Spring WebFlux

Stability: Sustained Build Status Coverage Status Code Quality Release License

Problem Spring Web is a set of libraries that makes it easy to produce application/problem+json responses from a Spring application. It fills a niche, in that it connects the Problem library and either Spring Web MVC's exception handling or Spring WebFlux's exception handling so that they work seamlessly together, while requiring minimal additional developer effort. In doing so, it aims to perform a small but repetitive task β€” once and for all.

The way this library works is based on what we call advice traits. An advice trait is a small, reusable @ExceptionHandler implemented as a default method placed in a single method interface. Those advice traits can be combined freely and don't require to use a common base class for your @ControllerAdvice.

πŸ”Ž Please check out Baeldung: A Guide to the Problem Spring Web Library for a detailed introduction!

Features

  • lets you choose traits Γ  la carte
  • favors composition over inheritance
  • ~20 useful advice traits built in
  • Spring MVC and Spring WebFlux support
  • Spring Security support
  • customizable processing

Dependencies

  • Java 17
  • Any build tool using Maven Central, or direct download
  • Servlet Container for problem-spring-web or
  • Reactive, non-blocking runtime for problem-spring-webflux
  • Spring 6 (or Spring Boot 3) users may use version >= 0.28.0
  • Spring 5 (or Spring Boot 2) users may use version 0.27.0
  • Spring 4 (or Spring Boot 1.5) users may use version 0.23.0
  • Spring Security 5 (optional)
  • Failsafe 2.3.3 (optional)

Installation and Configuration

Customization

The problem handling process provided by AdviceTrait is built in a way that allows for customization whenever the need arises. All of the following aspects (and more) can be customized by implementing the appropriate advice trait interface:

Aspect Method(s) Default
Creation AdviceTrait.create(..)
Logging AdviceTrait.log(..) 4xx as WARN, 5xx as ERROR including stack trace
Content Negotiation AdviceTrait.negotiate(..) application/json, application/*+json, application/problem+json and application/x.problem+json
Fallback AdviceTrait.fallback(..) application/problem+json
Post-Processing AdviceTrait.process(..) n/a

The following example customizes the MissingServletRequestParameterAdviceTrait by adding a parameter extension field to the Problem:

@ControllerAdvice
public class MissingRequestParameterExceptionHandler implements MissingServletRequestParameterAdviceTrait {
    @Override
    public ProblemBuilder prepare(Throwable throwable, StatusType status, URI type) {
        var exception = (MissingServletRequestParameterException) throwable;
        return Problem.builder()
                      .withTitle(status.getReasonPhrase())
                      .withStatus(status)
                      .withDetail(exception.getMessage())
                      .with("parameter", exception.getParameterName());
    }
}

Usage

Assuming there is a controller like this:

@RestController
@RequestMapping("/products")
class ProductsResource {

    @RequestMapping(method = GET, value = "/{productId}", produces = APPLICATION_JSON_VALUE)
    public Product getProduct(String productId) {
        // TODO implement
        return null;
    }

    @RequestMapping(method = PUT, value = "/{productId}", consumes = APPLICATION_JSON_VALUE)
    public Product updateProduct(String productId, Product product) {
        // TODO implement
        throw new UnsupportedOperationException();
    }

}

The following HTTP requests will produce the corresponding response respectively:

GET /products/123 HTTP/1.1
Accept: application/xml
HTTP/1.1 406 Not Acceptable
Content-Type: application/problem+json

{
  "title": "Not Acceptable",
  "status": 406,
  "detail": "Could not find acceptable representation"
}
POST /products/123 HTTP/1.1
Content-Type: application/json

{}
HTTP/1.1 405 Method Not Allowed
Allow: GET
Content-Type: application/problem+json

{
  "title": "Method Not Allowed",
  "status": 405,
  "detail": "POST not supported"
}

Stack traces and causal chains

Before you continue, please read the section about Stack traces and causal chains in zalando/problem.

In case you want to enable stack traces, please configure your ProblemModule as follows:

ObjectMapper mapper = new ObjectMapper()
    .registerModule(new ProblemModule().withStackTraces());

Causal chains of problems are disabled by default, but can be overridden if desired:

@ControllerAdvice
class ExceptionHandling implements ProblemHandling {

    @Override
    public boolean isCausalChainsEnabled() {
        return true;
    }

}

Note Since you have full access to the application context at that point, you can externalize the configuration to your application.yml and even decide to reuse Spring's server.error.include-stacktrace property.

Enabling both features, causal chains and stacktraces, will yield:

{
  "title": "Internal Server Error",
  "status": 500,
  "detail": "Illegal State",
  "stacktrace": [
    "org.example.ExampleRestController.newIllegalState(ExampleRestController.java:96)",
    "org.example.ExampleRestController.nestedThrowable(ExampleRestController.java:91)"
  ],
  "cause": {
    "title": "Internal Server Error",
    "status": 500,
    "detail": "Illegal Argument",
    "stacktrace": [
      "org.example.ExampleRestController.newIllegalArgument(ExampleRestController.java:100)",
      "org.example.ExampleRestController.nestedThrowable(ExampleRestController.java:88)"
    ],
    "cause": {
      "title": "Internal Server Error",
      "status": 500,
      "detail": "Null Pointer",
      "stacktrace": [
        "org.example.ExampleRestController.newNullPointer(ExampleRestController.java:104)",
        "org.example.ExampleRestController.nestedThrowable(ExampleRestController.java:86)",
        "sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
        "sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)",
        "sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)",
        "java.lang.reflect.Method.invoke(Method.java:483)",
        "org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)",
        "org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)",
        "org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)",
        "org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)",
        "org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)",
        "org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)",
        "org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)",
        "org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)",
        "org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)",
        "org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)",
        "org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)",
        "org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)",
        "org.junit.runners.ParentRunner.run(ParentRunner.java:363)",
        "org.junit.runner.JUnitCore.run(JUnitCore.java:137)",
        "com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)",
        "com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)",
        "com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)"
      ]
    }
  }
}

Known Issues

Spring allows to restrict the scope of a @ControllerAdvice to a certain subset of controllers:

@ControllerAdvice(assignableTypes = ExampleController.class)
public final class ExceptionHandling implements ProblemHandling

By doing this you'll loose the capability to handle certain types of exceptions namely:

  • HttpRequestMethodNotSupportedException
  • HttpMediaTypeNotAcceptableException
  • HttpMediaTypeNotSupportedException
  • NoHandlerFoundException

We inherit this restriction from Spring and therefore recommend to use an unrestricted @ControllerAdvice.

Getting Help

If you have questions, concerns, bug reports, etc., please file an issue in this repository's Issue Tracker.

Getting Involved/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.

Credits and references

problem-spring-web's People

Contributors

aafwu00 avatar alexanderyastrebov avatar bocytko avatar buttilda avatar c00ler avatar cberg-zalando avatar cbornet avatar cemo avatar chicobento avatar danielfran avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar github-actions[bot] avatar joshiste avatar kakawait avatar lappleapple avatar lucailmartini avatar lukasniemeier-zalando avatar malpi avatar pascalschumacher avatar rud avatar sangdol avatar seesharpcode avatar tcharl avatar thehilikus avatar torwunder avatar whiskeysierra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

problem-spring-web's Issues

Spring Security support

Spring security, e.g. OAuth, produces its own error objects (at least this is what I see on our test services).

Enhance documentation for Spring-boot & Jackson

By simply following README.md with a fresh Spring Boot application output will looks like (stackTrace was skipped)

{
"cause": null,
"stackTrace": [...],
"type": "http://httpstatus.es/405",
"title": "Method Not Allowed",
"status": "METHOD_NOT_ALLOWED",
"detail": {
"present": true
},
"instance": {
"present": false
},
"parameters": {},
"message": null,
"localizedMessage": null,
"suppressed": []
}

I know it's about Jackson configuration and precious information is related on https://github.com/zalando/problem. But it's not really user friendly to switch between the two projects to successfully find all documentation I need for my Spring project.

Moreover I know that configuration can depends on many things: using Jackson or not? Using Spring boot or not?

But we can add some sample like following (Spring boot + Jackson)

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web</artifactId>
    <version>${problem-spring-web.version}</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
    <version>${jackson-datatype-jdk8.version}</version>
</dependency> 
@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
    return new Jackson2ObjectMapperBuilder().modules(new Jdk8Module(), new ProblemModule());
}

Will display

{
"type": "http://httpstatus.es/405",
"title": "Method Not Allowed",
"status": 405,
"detail": "Request method 'GET' not supported"
}

Validation, but not 422?

In common case we used to validated entity from POST, PUT, etc.. Thus when validation exception is triggered the good way is to return 422 Unprocessable Entity !

However sometime we can validate query param like following API:

/api/feed?url=XXX

Where url is mandatory param with following validation:

  1. Should not be empty
  2. Should be a valid URL

For 1. we can simply return a MissingServletRequestParameterException that is correctly manage by problem-spring-web but for 2. I don't think 422 Unprocessable Entity is a correct status. Because url and query param is not part of request entity.

I was thinking about returning 400 status but with validation problem body

{
  "violations": [
    {
      "field": "url",
      "message": "must be a valid URL"
    }
  ],
  "type": "<URI>",
  "status": 400,
  "title": "Constraint Violation"
}

Do you think is a good idea? If yes how can I reuse existing ConstraintViolationProblem (final class)

Problem with problem :) with kotlin

Firstly, I'm new to Kotlin, so this issue might be kind of stupid.

The problem is that fields like cause, stackTrace, localizedMessage and suppressed ale always serialized.
I red that server.error.include-stacktrace is disabled by default but it doesn't seem to work.
So example problem (json) looks like this (always):

{
  "cause": null,
  "stackTrace": [...],
  "status": "BAD_REQUEST",
  "detail": null,
  "violations": [
    {
      "field": "email",
      "message": "not a well-formed email address"
    }
  ],
  "title": "Constraint Violation",
  "type": "https://zalando.github.io/problem/constraint-violation",
  "message": "Constraint Violation",
  "instance": null,
  "parameters": {},
  "localizedMessage": "Constraint Violation",
  "suppressed": []
}

Workaround for this is to implement custom Problem and override process method:

class DefaultProblem(private val type: URI,
                     private val title: String?,
                     private val status: Response.StatusType?,
                     private val parameters: MutableMap<String, Any>,
                     private val instance: URI?,
                     private val detail: String?) : Problem {
    override fun getType(): URI = type
    override fun getTitle(): String? = title
    override fun getStatus(): Response.StatusType? = status
    @JsonAnyGetter
    override fun getParameters(): MutableMap<String, Any> = parameters
    override fun getInstance(): URI? = instance
    override fun getDetail(): String? = detail
}

@ControllerAdvice
class ExceptionHandler : ProblemHandling {

    @ExceptionHandler(UserAlreadyExistException::class)
    fun handleUserAlreadyExistException(exception: UserAlreadyExistException, nativeWebRequest: NativeWebRequest) =
            create(Problem.builder()
                    .withStatus(Response.Status.CONFLICT)
                    .build(),
                    nativeWebRequest)!!

    override fun process(entity: ResponseEntity<Problem>?): ResponseEntity<Problem> {
        val problem = entity!!.body
        val headers = entity.headers
        val problemWithoutStackTrace = createProblemWithoutStackTrace(problem)

        return ResponseEntity.status(problem.status!!.statusCode)
                .headers(headers)
                .contentType(headers.contentType)
                .body(problemWithoutStackTrace)
    }

    private fun createProblemWithoutStackTrace(problem: Problem): DefaultProblem {
        val parameters = HashMap<String,Any>()
        if (problem is ConstraintViolationProblem) {
            parameters.put("violations", problem.violations)
        }
        return DefaultProblem(
                type = problem.type,
                title = problem.title,
                status = problem.status,
                parameters = parameters,
                instance = problem.instance,
                detail = problem.detail)
    }
}

And the result is:

{
  "type": "https://zalando.github.io/problem/constraint-violation",
  "title": "Constraint Violation",
  "status": "BAD_REQUEST",
  "instance": null,
  "detail": null,
  "violations": [
    {
      "field": "email",
      "message": "not a well-formed email address"
    }
  ]
}

Am I doing something wrong?
Working example can be found here: https://github.com/sta-szek/squash-zg

BTW is there any way to customize your types e.g.:
https://zalando.github.io/problem/constraint-violation replace with:
Types.CONSTRAINT_VIOLATION="my-custom-type-goes-here" ?

toStatus and isCasualChainEnabled serialization

When I was writing my own ThrowableProblem I had to implement

Response.StatusType getStatus();

However to do not use JaxRS status I added SpringAdviceTrait implements on my custom ThrowableProblem in order to use

default StatusType toStatus(HttpStatus status) {
    return new HttpStatusAdapter(status);
}

However by doing such thing my custom ThrowableProblem becomes an AdviceTrait and

isCausalChainsEnabled();

is serialized to the response.

{"causalChainsEnabled":false,"status":502,"type":"https://httpstatuses.com/502","detail":"Following upstream host \"httpbin.or\" is unknown","title":"Upstream host problem"}

Related to #42

Since HttpStatusAdapter is package scope there is no real alternative instead using JaxRS

Render StackTrace if configured (not default behavior)

I would to know if it's possible to render stackTrace on JSON error output. But with something configurable.

Spring boot offers properties server.error.include-stacktrace to include or not stackTrace on whitelabel error page. I would have some behavior on API for easily debugging during development phase.

Could be manage by JsonFilter or something smarter.

Make ConstraintViolationProblem more customizable

Currently we can customize defaultConstraintViolationType, defaultConstraintViolationStatus or formatFieldName, but I think that customization of violations is missing.
It would be nice if I could rename violations field and also fields in Violation class.

I think the best way is to make defaultConstraintViolationProblem similar to defaultConstraintViolationType.
Moreover we must provide violations preprocessing (probably in here) similar to this one as it would provide possibility to change Violation class to my custom one.

To acheive this Violation and ConstraintViolationProblem should not be final.

The final result should allow me to make structure as follows:

"validation-errors": [
    {
      "context-key": "fieldName",
      "message-key": "not-null"
    }
]

NoHandlerFoundException is not triggered / missing documentation

First: great library!

I noticed that the NoHandlerFoundAdviceTrait is not triggered without the following config:

spring:
  resources:
    add-mappings: false
  mvc:
    throw-exception-if-no-handler-found: true

This seems to happen because ResourceHttpRequestHandler sends a 404 response. The throw-exception-if-no-handler-found is already documented in the NoHandlerFoundAdviceTrait class. However, there is no mention about the add-mappings which is mandatory as well.

Minor logging issue during tests

SLF4J: The following set of substitute loggers may have been accessed
SLF4J: during the initialization phase. Logging calls during this
SLF4J: phase were not honored. However, subsequent logging calls to these
SLF4J: loggers will work as normally expected.
SLF4J: See also http://www.slf4j.org/codes.html#substituteLogger
SLF4J: org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

Enhance readme

Should include examples and descriptions on how we want advices to be used.

BindException validation built-in support

I would discuss with you if org.springframework.validation.BindException could be a great candidate to be a built-in managed exception.

Indeed when you're validating query params rather than post body like following

@RequestMapping(path = "/handler-invalid-query-strings", method = GET)
public ResponseEntity<String> validRequestQueryStrings(@Valid final PageRequest pageRequest) {
    return ResponseEntity.ok("done");
}

With PageRequest looks like

@Data
public final class PageRequest {

    @Min(value = 0)
    private int page = 0;

    @Min(value = 1)
    private int size = 50;
}

If you try

@Test
public void invalidRequestQueryParams() throws Exception {
    mvc().perform(request(GET, "http://localhost/api/handler-invalid-query-strings?page=-1"))
         .andExpect(status().isBadRequest());
}

I will fail because 500 status code will be returned after Spring will throw org.springframework.validation.BindException


A bit off-topic, but can explain why:

Spring MethodValidationPostProcessor works great with zalando:problem-spring-web, indeed if you replace previous GetMapping with following:

@RequestMapping(path = "/handler-invalid-query-strings", method = GET)
public ResponseEntity<String> validRequestQueryStrings(@Min(value = 0) @RequestParam(value = "page", defaultValue = "0") int page,
        @Min(value = 1) @RequestParam(value="limit", defaultValue = "50") int limit) {
    return ResponseEntity.ok("done");
}

I will work but since method parameter name is removed after compilation the result looks like:

{  
   "violations":[  
      {  
         "field":"getUsers.arg0",
         "message":"must be greater than or equal to 0"
      }
   ],
   "status":400,
   "type":"https://zalando.github.io/problem/constraint-violation",
   "title":"Constraint Violation"
}

As you can see field is equals to getUsers.arg0 which is no really user-friendly.
That why I used to create a intermediate object (DTO?) for validation even for GET parameters.

Java 7 compatibility

This library does EXACTLY what I am looking for but my app is deployed on GAE Standard Environment and it doesn't support java 8.

In order to use problem-spring-web, I will have to fork the repository + problem, make both compatible with my env and then start using it.

Do you have any advice for me before I get started? Are you planning to support java 7 on the near future?

Thanks

Response MediaType negotiation

I updated from problem-spring-web:0.5.0 to problem-spring-web:0.11.0and found that the response content type negotiation was changed in a incompatible way.

Before in org.zalando.problem.spring.web.advice.MediaType:

static Optional<MediaType> determineContentType(final NativeWebRequest request) {
        try {
            final List<MediaType> acceptedMediaTypes = headerNegotiator.resolveMediaTypes(request);

            if (acceptedMediaTypes.isEmpty()) {
                return Optional.of(PROBLEM);
            }

            if (acceptedMediaTypes.stream().anyMatch(PROBLEM::isCompatibleWith)) {
                return Optional.of(PROBLEM);
            }

            if (acceptedMediaTypes.stream().anyMatch(X_PROBLEM::isCompatibleWith)) {
                return Optional.of(X_PROBLEM);
            }

            if (acceptedMediaTypes.stream().anyMatch(WILDCARD_JSON::isCompatibleWith)) {
                return Optional.of(PROBLEM);
            }

            if (acceptedMediaTypes.stream().anyMatch(APPLICATION_JSON::isCompatibleWith)) {
                return Optional.of(PROBLEM);
            }

        } catch (final HttpMediaTypeNotAcceptableException exception) {
            LOG.info("Unable to determine content type due to error during parsing Accept header: [{}]", exception);
        }
        return Optional.empty();
    }

Now in org.zalando.problem.spring.web.advice.AdviceTrait:

default Optional<MediaType> negotiate(final NativeWebRequest request) {
        final HeaderContentNegotiationStrategy negotiator = new HeaderContentNegotiationStrategy();

        final List<MediaType> mediaTypes = negotiator.resolveMediaTypes(request);

        if (mediaTypes.isEmpty()) {
            return Optional.of(PROBLEM);
        }

        for (final MediaType mediaType : mediaTypes) {
            if (APPLICATION_JSON.isCompatibleWith(mediaType)) {
                return Optional.of(PROBLEM);
            } else if (PROBLEM.isCompatibleWith(mediaType)) {
                return Optional.of(PROBLEM);
            } else if (X_PROBLEM.isCompatibleWith(mediaType)) {
                return Optional.of(X_PROBLEM);
            } else if (WILDCARD_JSON.isCompatibleWith(mediaType)) {
                return Optional.of(PROBLEM);
            }
        }

        return Optional.empty();
    }

If the request has header Accept: application/x.something+json,application/x.problem+json and a ThrowableProblem is thrown, the previous implementation would correctly return application/x.problem+json, where the new one incorrectly returns application/problem+json, because the WILDCARD_JSON will always match in the first loop.

The previous and new implementation seem equals, but they are in fact functionally different.

Support RequestRejectedException

Add support for org.springframework.security.web.firewall.RequestRejectedException, which is, for example, thrown by the org.springframework.security.web.firewall.DefaultHttpFirewall in case a non-normalized path is requested.

Problem with disabling stacktrace

My code is:

@Configuration
public class ProblemJsonConfiguration {
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper().registerModule(new ProblemModule().withStackTraces(false));
    }
}
@ControllerAdvice
public class ExceptionHandler implements ProblemHandling { ... }

and response always contains field stackTrace:

{
  "cause": null,
  "stackTrace": [
    {
      "methodName": "resolveArgument",
      "fileName": "AbstractNamedValueMethodArgumentResolver.java",
      "lineNumber": 123,
      "className": "org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver",
      "nativeMethod": false
    },...
  ],
  "type": "about:blank",
  "title": "Bad Request",
  "status": "BAD_REQUEST",
  "detail": "Failed to convert value of type [java.lang.String] to required type ...",
  "instance": null,
  "parameters": {},
  "message": "Bad Request: Failed to convert value of type [java.lang.String] to required type ...",
  "localizedMessage": "Bad Request: Failed to convert value of type [java.lang.String] to required type ...",
  "suppressed": []
}

I followed steps on https://github.com/zalando/problem-spring-web/blob/master/README.md#stack-traces-and-causal-chains

Tried this:

@ControllerAdvice
class ExceptionHandling implements ProblemHandling {
    @Override
    public boolean isCausalChainsEnabled() {
        return false;
    }
}

and this: server.error.include-stacktrace=NEVER

There is no way to disable stactTrace. Am I doing something wrong?

BTW,
Is there possibility to rename and disable some fields?

Make TYPE_VALUE customizable

Currently there is no (easy) way to replace zalando's TYPE_VALUE's e.g.:
public static final String TYPE_VALUE = "https://zalando.github.io/problem/constraint-violation";

Why?
I want custom problem description for my developers / end users

ProblemAuthenticationEntryPoint in spring-boot app

Integrating the ProblemAuthenticationEntryPoint as described in the README leads to the following exception on starting a spring-boot application:

***************************
APPLICATION FAILED TO START
***************************

Description:

Field resolver in org.zalando.problem.spring.web.advice.security.ProblemAuthenticationEntryPoint required a single bean, but 2 were found:
        - errorAttributes: defined by method 'errorAttributes' in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.class]
        - handlerExceptionResolver: defined by method 'handlerExceptionResolver' in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Which one to pick?

Can not find org.zalando.problem.MoreStatus

Hi, one of my teammates tried using this package and keeps getting the following error on startup:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.fasterxml.jackson.databind.Module]: Factory method 'problemModule' threw exception; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.zalando.problem.MoreStatus

She has tried it with both of the following pom.xml dependencies:

<dependency>
  <groupId>org.zalando</groupId>
  <artifactId>problem-spring-web</artifactId>
  <version>0.18.0</version>
</dependency>

And

<dependency>
  <groupId>org.zalando</groupId>
  <artifactId>problem-spring-web</artifactId>
  <version>0.18.0</version>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem</artifactId>
    <version>0.11.0</version>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>jackson-datatype-problem</artifactId>
    <version>0.11.0</version>
</dependency>

Are we missing an additional dependency?

Enhance documentation about conflicts with spring security

When this library is used in combination with spring security there are some conflicts that arise. Although the documentation mentions this already it is not easily possible to fix the problems. Therefore I want to enhance the documentation, so that it is more clear what is needed to resolve the issues.

Tomcat Stacktrace is rendered as response

When we use problem-spring-web with tomcat servlet container and WITHOUT https://github.com/zalando/logbook we encounter the following behaviour:

Request with specified unsupported Accept header:

curl -v -H "Accept: text/xml" http://localhost:8080/hello
> GET /hello HTTP/1.1
> Host: localhost:8080
> Accept: text/xml

Results in the following response:

< HTTP/1.1 500
< Content-Type: text/html;charset=utf-8

<!DOCTYPE html><html><head><title>Apache Tomcat/8.5.5 - Error report</title><style type="text/css">H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}.line {height: 1px; background-color: #525D76; border: none;}</style> </head><body><h1>HTTP Status 500 - foo</h1><div class="line"></div><p><b>type</b> Exception report</p><p><b>message</b> <u>foo</u></p><p><b>description</b> <u>The server encountered an internal error that prevented it from fulfilling this request.</u></p><p><b>exception</b></p><pre>java.lang.IllegalArgumentException: foo
	de.foo.bar.api.Hello.hello(Hello.java:11)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:483)
	org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
	org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
	org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
	org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
	org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
	org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	org.zalando.logbook.servlet.NormalStrategy.doFilter(NormalStrategy.java:42)
	org.zalando.logbook.servlet.LogbookFilter.doFilter(LogbookFilter.java:33)
	org.zalando.logbook.servlet.HttpFilter.doFilter(HttpFilter.java:32)
	org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:105)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
	org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
	org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:121)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
	org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
	org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
	org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
	org.zalando.logbook.servlet.SecurityStrategy.doFilter(SecurityStrategy.java:32)
	org.zalando.logbook.servlet.LogbookFilter.doFilter(LogbookFilter.java:33)
	org.zalando.logbook.servlet.HttpFilter.doFilter(HttpFilter.java:32)
	org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:107)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	org.zalando.tracer.servlet.TracerFilter.doFilter(TracerFilter.java:33)
	org.zalando.tracer.servlet.HttpFilter.doFilter(HttpFilter.java:28)
* Closing connection 0
</pre><p><b>note</b> <u>The full stack trace of the root cause is available in the Apache Tomcat/8.5.5 logs.</u></p><hr class="line"><h3>Apache Tomcat/8.5.5</h3></body></html>%

When you include https://github.com/zalando/logbook and enable trace logging
<Logger name="org.zalando.logbook.Logbook" level="trace"/>
the stack trace won't be rendered anymore inside the response and the response code changes from 500 to 406.

How to extend the built-in problemtypes for all advicetraits?

Hi,

I want to use your library and want to enrich each problem returned for standard spring errors with more information like a problem-id which is logged in the background with some more information.
How can I do that? I donΒ΄t see any possibility
Currently I have to include your sourcecode into my project and adapt the code to my needs...

Thanks,
Jens

Handle javax.validation.ConstraintViolationException

Right now only the Spring specific MethodArgumentNotValidException is handled, might be useful to handle ConstraintViolationException of javax.validation - which would add a dependency (or have it optional?)

Make Responses.create(..) methods customizable

Instead of having all traits delegate to a static class, we should move those factory methods to it's own interface (using default methods) and let every advice trait extend it. That way concrete controller advice implementations are free to override it based on their needs.

Add maven-wrapper

It might make sense to use maven-wrapper to simplify setup of the project.

Public API JDK vanilla

What do you think about the idea of exposing only (or at least) JDK vanilla compliant API?

Example without using ImmutableList from guava or offer an alternative with List

Support ValidationException

What do we think of adding support for the base validation exception ValidationException as we already support one implementor ConstraintViolationException?

Support AccessDeniedException

AccessDeniedException is common exception from Spring security when trying to access to authorize API but without any authentication or anonymous (and maybe others).

I know problem-spring-web is like a framework but it should at least support basic/common Spring security exception?

Expected:

  • status: 401

FieldError createViolation discussion

Just a question, is there a reason to generate fieldName like

final String fieldName = error.getObjectName() + "." + error.getField();

I'm questioning myself about error.getObjectName() since problem-spring-web has been designed to expose errors through http to client.

From what I experimented error.getObjectName() represents Java method name or Java simple class name like:

UserDto or getUser if you are using MethodValidationPostProcessor

Generally I won't leak any internal information to client, even more that it seems not really useful for him.

What do you think about?

Support Spring HttpStatus

Not really useful and not a great betterment (feel free to close it! I can really continue without, for sure)! But I could be cool that AdviceTrait support org.springframework.http.HttpStatus in addition to javax.ws.rs.core.StatusType (because we are speaking about problem-spring-web)

But I don't think you should support org.springframework.http.HttpStatus on ProblemBuilder (or other class in Problem package) because is not problem-spring-web package I was just thinking about offer following default

ResponseEntity<Problem> create(final StatusType status, final Throwable throwable, final NativeWebRequest request) {
    ...
}

ResponseEntity<Problem> create(final HttpStatus status, final Throwable throwable, final NativeWebRequest request) {
    // Convert HttpStatus to StatusType
    return create(convert(status), throwable, request);
}

And same for other default


PS: I can submit a PR if you think is valuable

Handle specific reasons of HttpMessageNotReadableException

Something like this

    @ExceptionHandler
    public ResponseEntity<Problem> onHttpMessageNotReadable(final HttpMessageNotReadableException e) throws Throwable {
        final Throwable cause = e.getCause();
        if (cause == null) {
            return onException(e);
        } else if (cause instanceof JsonParseException) {
            return onParseException((JsonParseException) cause);
        } else if (cause instanceof JsonMappingException) {
            return onMappingException((JsonMappingException) cause);
        } else {
             ...
        }
    }

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.