Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions docs/asciidoc/problem-details.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,25 @@ problem.details {

==== Creating problems

`HttpProblem` class represents the `RFC 7807` model. It is the main entity you need to work with to produce the problem.
javadoc:problem.HttpProblem[] class represents the `RFC 7807` model. It is the main entity you need to work with to produce the problem.

===== Static helpers

There are several handy static methods to produce a simple `HttpProblem`:
There are several handy static methods to produce a simple javadoc:problem.HttpProblem[]:

- `HttpProblem.valueOf(StatusCode status)` - will pick the title by status code.
- javadoc:problem.HttpProblem[valueOf, io.jooby.StatusCode] - will pick the title by status code.
Don't overuse it, the problem should have meaningful `title` and `detail` when possible.
- `HttpProblem.valueOf(StatusCode status, String title)` - with custom `title`
- `HttpProblem.valueOf(StatusCode status, String title, String detail)` - with `title` and `detail`
- javadoc:problem.HttpProblem[valueOf, io.jooby.StatusCode, java.lang.String] - with custom `title`
- javadoc:problem.HttpProblem[valueOf, io.jooby.StatusCode, java.lang.String, java.lang.String] - with `title` and `detail`

`HttpProblem` extends `RuntimeException` so you can naturally throw it (as you do with exceptions):
and a couple of shorthands for the most common validation codes:

- javadoc:problem.HttpProblem[badRequest, java.lang.String, java.lang.String] - for _400 Bad Request_
- javadoc:problem.HttpProblem[notFound, java.lang.String, java.lang.String] - for _404 Not Found_
- javadoc:problem.HttpProblem[unprocessableEntity, java.lang.String, java.lang.String] - for _422 Unprocessable Entity_
- javadoc:problem.HttpProblem[internalServerError] - for _500 Internal Server Error_

javadoc:problem.HttpProblem[] extends `RuntimeException` so you can naturally throw it (as you do with exceptions):

.Java
[source,java,role="primary"]
Expand Down Expand Up @@ -130,7 +137,7 @@ throw HttpProblem.builder()

`RFC 7807` has a simple extension model: APIs are free to add any other properties to the problem details object, so all properties other than the five ones listed above are extensions.

However, variadic root level fields are usually not very convenient for (de)serialization (especially in statically typed languages). That's why `HttpProblem` implementation grabs all extensions under a single root field `parameters`. You can add parameters using builder like this:
However, variadic root level fields are usually not very convenient for (de)serialization (especially in statically typed languages). That's why javadoc:problem.HttpProblem[] implementation grabs all extensions under a single root field `parameters`. You can add parameters using builder like this:

[source,java]
----
Expand Down Expand Up @@ -165,7 +172,7 @@ Resulting response:

==== Adding headers

Some `HTTP` codes (like `413` or `426`) require additional response headers, or it may be required by third-party system/integration. `HttpProblem` support additional headers in response:
Some `HTTP` codes (like `413` or `426`) require additional response headers, or it may be required by third-party system/integration. javadoc:problem.HttpProblem[] support additional headers in response:

[source,java]
----
Expand Down Expand Up @@ -211,12 +218,12 @@ In response:

[TIP]
====
If you need to enrich errors with more information feel free to extend `HttpProblem.Error` and make your custom errors model.
If you need to enrich errors with more information feel free to extend javadoc:problem.HttpProblem.Error[] and make your custom errors model.
====

==== Custom `Exception` to `HttpProblem`

Apparently, you may already have many custom `Exception` classes in the codebase, and you want to make them `Problem Details` compliant without complete re-write. You can achieve this by implementing `HttpProblemMappable` interface. It allows you to control how exceptions should be transformed into `HttpProblem` if default behaviour doesn't suite your needs:
Apparently, you may already have many custom `Exception` classes in the codebase, and you want to make them `Problem Details` compliant without complete re-write. You can achieve this by implementing javadoc:problem.HttpProblemMappable[] interface. It allows you to control how exceptions should be transformed into javadoc:problem.HttpProblem if default behaviour doesn't suite your needs:

[source,java]
----
Expand All @@ -235,7 +242,7 @@ public class MyException implements HttpProblemMappable {

==== Custom Problems

Extending `HttpProblem` and utilizing builder functionality makes it really easy:
Extending javadoc:problem.HttpProblem[] and utilizing builder functionality makes it really easy:

[source,java]
----
Expand Down
3 changes: 1 addition & 2 deletions docs/pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Expand All @@ -11,7 +10,7 @@

<properties>
<application.class>io.jooby.adoc.DocApp</application.class>
<jooby.version>3.7.0</jooby.version>
<jooby.version>4.0.3</jooby.version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you

<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</maven.compiler.release>
Expand Down
10 changes: 5 additions & 5 deletions docs/src/main/java/io/jooby/adoc/DocApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.List;

import io.jooby.LoggingService;
import io.jooby.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
Expand All @@ -28,10 +29,6 @@ public class DocApp extends Jooby {
}

{
ServerOptions server = new ServerOptions();
server.setPort(4000);
setServerOptions(server);

Path site = DocGenerator.basedir().resolve("asciidoc").resolve("site");
assets("/*", site);

Expand All @@ -52,7 +49,10 @@ public static void main(String[] args) throws Exception {

DocGenerator.generate(basedir, false, Arrays.asList(args).contains("v1"), true);

runApp(args, DocApp::new);
Server server = Server.loadServer();
server.setOptions(new ServerOptions().setPort(4000));

runApp(args, server, DocApp::new);

Path outdir = basedir.resolve("asciidoc").resolve("site");

Expand Down
146 changes: 144 additions & 2 deletions jooby/src/main/java/io/jooby/problem/HttpProblem.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,136 @@ private static String createMessage(String title, String detail) {
return Objects.isNull(detail) ? title : title + ": " + detail;
}

/**
* Creates an HttpProblem with the specified status code and custom title.
*
* @param status the HTTP status code for this problem
* @param title the title describing the type of problem
* @return an HttpProblem instance with the specified status and title
*/
public static HttpProblem valueOf(StatusCode status, String title) {
return builder().title(title).status(status).build();
}

/**
* Creates an HttpProblem with the specified status code, custom title, and detail.
*
* @param status the HTTP status code for this problem
* @param title the title describing the type of problem
* @param detail a human-readable explanation specific to this occurrence of the problem
* @return an HttpProblem instance with the specified status, title, and detail
*/
public static HttpProblem valueOf(StatusCode status, String title, String detail) {
return builder().title(title).status(status).detail(detail).build();
}

/**
* Creates an HttpProblem with the specified status code using the default title.
* The title is automatically set to the status code's reason phrase.
*
* @param status the HTTP status code for this problem
* @return an HttpProblem instance with the specified status and default title
*/
public static HttpProblem valueOf(StatusCode status) {
return builder().title(status.reason()).status(status).build();
}

/**
* Creates an HttpProblem with BAD_REQUEST status (400) and custom title and detail.
*
* @param title the title describing the type of problem
* @param detail a human-readable explanation specific to this occurrence of the problem
* @return an HttpProblem instance with BAD_REQUEST status
*/
public static HttpProblem badRequest(String title, String detail) {
return builder()
.title(title)
.status(StatusCode.BAD_REQUEST)
.detail(detail)
.build();
}

/**
* Creates an HttpProblem with BAD_REQUEST status (400) using the default title.
* The title is automatically set to the status code's reason phrase.
*
* @param detail a human-readable explanation specific to this occurrence of the problem
* @return an HttpProblem instance with BAD_REQUEST status
*/
public static HttpProblem badRequest(String detail) {
return builder()
.title(StatusCode.BAD_REQUEST.reason())
.status(StatusCode.BAD_REQUEST)
.detail(detail)
.build();
}

/**
* Creates an HttpProblem with NOT_FOUND status (404) and custom title and detail.
*
* @param title the title describing the type of problem
* @param detail a human-readable explanation specific to this occurrence of the problem
* @return an HttpProblem instance with NOT_FOUND status
*/
public static HttpProblem notFound(String title, String detail) {
return builder()
.title(title)
.status(StatusCode.NOT_FOUND)
.detail(detail)
.build();
}

/**
* Creates an HttpProblem with NOT_FOUND status (404) using the default title.
* The title is automatically set to the status code's reason phrase.
*
* @param detail a human-readable explanation specific to this occurrence of the problem
* @return an HttpProblem instance with NOT_FOUND status
*/
public static HttpProblem notFound(String detail) {
return builder()
.title(StatusCode.NOT_FOUND.reason())
.status(StatusCode.NOT_FOUND)
.detail(detail)
.build();
}

/**
* Creates an HttpProblem with UNPROCESSABLE_ENTITY status (422) and custom title and detail.
*
* @param title the title describing the type of problem
* @param detail a human-readable explanation specific to this occurrence of the problem
* @return an HttpProblem instance with UNPROCESSABLE_ENTITY status
*/
public static HttpProblem unprocessableEntity(String title, String detail) {
return builder()
.title(title)
.status(StatusCode.UNPROCESSABLE_ENTITY)
.detail(detail)
.build();
}

/**
* Creates an HttpProblem with UNPROCESSABLE_ENTITY status (422) using the default title.
* The title is automatically set to the status code's reason phrase.
*
* @param detail a human-readable explanation specific to this occurrence of the problem
* @return an HttpProblem instance with UNPROCESSABLE_ENTITY status
*/
public static HttpProblem unprocessableEntity(String detail) {
return builder()
.title(StatusCode.UNPROCESSABLE_ENTITY.reason())
.status(StatusCode.UNPROCESSABLE_ENTITY)
.detail(detail)
.build();
}

/**
* Creates an HttpProblem with SERVER_ERROR status (500) using predefined title and detail.
* This is typically used for unexpected server errors or misconfigurations.
*
* @return an HttpProblem instance with SERVER_ERROR status and predefined error message
*/
public static HttpProblem internalServerError() {
return builder()
.title(INTERNAL_ERROR_TITLE)
Expand Down Expand Up @@ -199,7 +317,8 @@ public static class Builder {
private final Map<String, Object> headers = new LinkedHashMap<>();
private final List<Error> errors = new LinkedList<>();

Builder() {}
Builder() {
}

public Builder type(@Nullable final URI type) {
this.type = type;
Expand Down Expand Up @@ -242,7 +361,7 @@ public Builder errors(final List<? extends Error> errors) {
}

/**
* @param key additional info parameter name
* @param key additional info parameter name
* @param value additional info parameter value
* @return this for chaining
*/
Expand All @@ -269,19 +388,42 @@ public HttpProblem build() {
}
}

/**
* Represents an individual error within an HTTP Problem response.
* This class encapsulates error details following RFC 7807/RFC 9457 standards
* for providing structured error information in API responses.
*/
public static class Error {
private final String detail;
private final String pointer;

/**
* Creates a new Error instance with the specified detail and pointer.
*
* @param detail a human-readable explanation of the specific error
* @param pointer a JSON Pointer (RFC 6901) that identifies the location
* in the request document where the error occurred
*/
public Error(String detail, String pointer) {
this.detail = detail;
this.pointer = pointer;
}

/**
* Returns the human-readable explanation of this specific error.
*
* @return the error detail message
*/
public String getDetail() {
return detail;
}

/**
* Returns a reference that identifies the location where this error occurred.
* This is typically a JSON Pointer (RFC 6901).
*
* @return the pointer to the location of the error
*/
public String getPointer() {
return pointer;
}
Expand Down