diff --git a/docs/asciidoc/problem-details.adoc b/docs/asciidoc/problem-details.adoc index c96452cfd2..9b4984ee80 100644 --- a/docs/asciidoc/problem-details.adoc +++ b/docs/asciidoc/problem-details.adoc @@ -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"] @@ -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] ---- @@ -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] ---- @@ -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] ---- @@ -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] ---- diff --git a/docs/pom.xml b/docs/pom.xml index ce8ab6d675..f7fa9cf624 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -1,4 +1,3 @@ - io.jooby.adoc.DocApp - 3.7.0 + 4.0.3 17 17 17 diff --git a/docs/src/main/java/io/jooby/adoc/DocApp.java b/docs/src/main/java/io/jooby/adoc/DocApp.java index e7e578add4..be766e489a 100644 --- a/docs/src/main/java/io/jooby/adoc/DocApp.java +++ b/docs/src/main/java/io/jooby/adoc/DocApp.java @@ -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; @@ -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); @@ -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"); diff --git a/jooby/src/main/java/io/jooby/problem/HttpProblem.java b/jooby/src/main/java/io/jooby/problem/HttpProblem.java index fde343f171..156f695a6f 100644 --- a/jooby/src/main/java/io/jooby/problem/HttpProblem.java +++ b/jooby/src/main/java/io/jooby/problem/HttpProblem.java @@ -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) @@ -199,7 +317,8 @@ public static class Builder { private final Map headers = new LinkedHashMap<>(); private final List errors = new LinkedList<>(); - Builder() {} + Builder() { + } public Builder type(@Nullable final URI type) { this.type = type; @@ -242,7 +361,7 @@ public Builder errors(final List errors) { } /** - * @param key additional info parameter name + * @param key additional info parameter name * @param value additional info parameter value * @return this for chaining */ @@ -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; }