wimdeblauwe / error-handling-spring-boot-starter

Spring Boot starter for configurable REST API error handling
https://wimdeblauwe.github.io/error-handling-spring-boot-starter/
Apache License 2.0
422 stars 51 forks source link

Support ProblemDetail #59

Open rajadilipkolli opened 1 year ago

rajadilipkolli commented 1 year ago

Spring is supporting new way to expose errors using ProblemDetail. Please support in the starter

wimdeblauwe commented 1 year ago

I had a look and I could write a custom serializer that uses ProblemDetail, but I am having trouble knowing how to map. By default, this library uses code and message like this:

{
  "code": "USER_NOT_FOUND",
  "message": "Could not find user with id 123"
}

The code seems to be lacking in the specification. The closest seems to be type, but that expects an URI which is not something we have available.

How do you envision this working?

wimdeblauwe commented 1 year ago

One idea might be to have the user configure a base URI for all error types. For example:

error.handling.rfc7807.base-uri=https://api.myservice.com/errors

Then the example could become:

{
    "type": "https://api.myservice.com/errors/user-not-found",
    "title": "Could not find user with id 123"
}

Note how we could use lower-snake-case to have the generated URI look nice.

wimdeblauwe commented 1 year ago

Another example:

{
  "code": "VALIDATION_FAILED",
  "message": "Validation failed for object='exampleRequestBody'. Error count: 2",
  "fieldErrors": [
    {
      "code": "INVALID_SIZE",
      "property": "name",
      "message": "size must be between 10 and 2147483647",
      "rejectedValue": "",
      "path": "name"
    },
    {
      "code": "REQUIRED_NOT_BLANK",
      "property": "favoriteMovie",
      "message": "must not be blank",
      "rejectedValue": null,
      "path": "favoriteMovie"
    }
  ]
}

Would become:

{
    "type": "https://api.myservice.com/errors/validation-failed",
    "title": "Validation failed for object='exampleRequestBody'. Error count: 2",
    "fieldErrors": [
    {
      "code": "INVALID_SIZE",
      "property": "name",
      "message": "size must be between 10 and 2147483647",
      "rejectedValue": "",
      "path": "name"
    },
    {
      "code": "REQUIRED_NOT_BLANK",
      "property": "favoriteMovie",
      "message": "must not be blank",
      "rejectedValue": null,
      "path": "favoriteMovie"
    }
  ]
}
rajadilipkolli commented 1 year ago

"code": "USER_NOT_FOUND",

We can achieve the same using problemDetail.setProperty("code", "USER_NOT_FOUND") so migration will be easier as we are retaining earlier key name

wimdeblauwe commented 1 year ago

The specification states:

Consumers MUST use the "type" string as the primary identifier for the problem type

So type is what code has always been for this library. A machine-readable string for consumers of the API to know the problem type.

I don't see any advantage of supporting ProblemDetail if we don't adhere to the specification.

bwgjoseph commented 1 year ago

It seems like type is an optional field?

When this member is not present, its value is assumed to be about:blank".

And it also states

The "about:blank" URI [RFC6694], when used as a problem type, indicates that the problem has no additional semantics beyond that of the HTTP status code.

When "about:blank" is used, the title SHOULD be the same as the recommended HTTP status phrase for that code (e.g., "Not Found" for 404, and so on), although it MAY be localized to suit client preferences (expressed with the Accept-Language request header).

Would that then be

{
    "title": "BAD_REQUEST",
    "fieldErrors": [
    {
      "code": "INVALID_SIZE",
      "property": "name",
      "message": "size must be between 10 and 2147483647",
      "rejectedValue": "",
      "path": "name"
    },
    {
      "code": "REQUIRED_NOT_BLANK",
      "property": "favoriteMovie",
      "message": "must not be blank",
      "rejectedValue": null,
      "path": "favoriteMovie"
    }
  ]
}
darioseidl commented 2 months ago

For reference, or for people how need problem support now, there is another library: https://github.com/zalando/problem-spring-web which configures https://github.com/zalando/problem for Spring Boot.

Yes, the type is optional. When absent or "about:blank", it doesn't say anything about the shape of the problem JSON, and "indicates that the problem has no additional semantics beyond that of the HTTP status code." so that means that the JSON should just look something like

{
  "title": "Not Found",
  "status": 404
}

In practice, it still works fine when additional fields are provided, like

{
  "title": "Not Found",
  "status": 404,
  "detail": "Enity foo id=1 not found"
}

IMO, the closest thing to your code is the problem title,

"title" (string) - A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization ...

When the type is present, it should be a URI that defines and explains the shape of the problem JSON. For example, the Zalando library uses this URL https://opensource.zalando.com/problem/constraint-violation/ as type for constraint violations.

Tl;dr: to support the problem RFC, this library could