Skip to content

Commit d25b50e

Browse files
authored
Merge pull request #3739 from kliushnichenko/feat/http-problem-shorthands
[HttpProblem] common validation shorthands
2 parents afd162e + 60017e0 commit d25b50e

File tree

4 files changed

+168
-20
lines changed

4 files changed

+168
-20
lines changed

docs/asciidoc/problem-details.adoc

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,25 @@ problem.details {
4646

4747
==== Creating problems
4848

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

5151
===== Static helpers
5252

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

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

60-
`HttpProblem` extends `RuntimeException` so you can naturally throw it (as you do with exceptions):
60+
and a couple of shorthands for the most common validation codes:
61+
62+
- javadoc:problem.HttpProblem[badRequest, java.lang.String, java.lang.String] - for _400 Bad Request_
63+
- javadoc:problem.HttpProblem[notFound, java.lang.String, java.lang.String] - for _404 Not Found_
64+
- javadoc:problem.HttpProblem[unprocessableEntity, java.lang.String, java.lang.String] - for _422 Unprocessable Entity_
65+
- javadoc:problem.HttpProblem[internalServerError] - for _500 Internal Server Error_
66+
67+
javadoc:problem.HttpProblem[] extends `RuntimeException` so you can naturally throw it (as you do with exceptions):
6168

6269
.Java
6370
[source,java,role="primary"]
@@ -130,7 +137,7 @@ throw HttpProblem.builder()
130137

131138
`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.
132139

133-
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:
140+
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:
134141

135142
[source,java]
136143
----
@@ -165,7 +172,7 @@ Resulting response:
165172

166173
==== Adding headers
167174

168-
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:
175+
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:
169176

170177
[source,java]
171178
----
@@ -211,12 +218,12 @@ In response:
211218

212219
[TIP]
213220
====
214-
If you need to enrich errors with more information feel free to extend `HttpProblem.Error` and make your custom errors model.
221+
If you need to enrich errors with more information feel free to extend javadoc:problem.HttpProblem.Error[] and make your custom errors model.
215222
====
216223

217224
==== Custom `Exception` to `HttpProblem`
218225

219-
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:
226+
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:
220227

221228
[source,java]
222229
----
@@ -235,7 +242,7 @@ public class MyException implements HttpProblemMappable {
235242

236243
==== Custom Problems
237244

238-
Extending `HttpProblem` and utilizing builder functionality makes it really easy:
245+
Extending javadoc:problem.HttpProblem[] and utilizing builder functionality makes it really easy:
239246

240247
[source,java]
241248
----

docs/pom.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
<?xml version="1.0" encoding="UTF-8"?>
32
<project xmlns="http://maven.apache.org/POM/4.0.0"
43
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -11,7 +10,7 @@
1110

1211
<properties>
1312
<application.class>io.jooby.adoc.DocApp</application.class>
14-
<jooby.version>3.7.0</jooby.version>
13+
<jooby.version>4.0.3</jooby.version>
1514
<maven.compiler.source>17</maven.compiler.source>
1615
<maven.compiler.target>17</maven.compiler.target>
1716
<maven.compiler.release>17</maven.compiler.release>

docs/src/main/java/io/jooby/adoc/DocApp.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.List;
1313

1414
import io.jooby.LoggingService;
15+
import io.jooby.Server;
1516
import org.slf4j.Logger;
1617
import org.slf4j.LoggerFactory;
1718
import org.slf4j.bridge.SLF4JBridgeHandler;
@@ -28,10 +29,6 @@ public class DocApp extends Jooby {
2829
}
2930

3031
{
31-
ServerOptions server = new ServerOptions();
32-
server.setPort(4000);
33-
setServerOptions(server);
34-
3532
Path site = DocGenerator.basedir().resolve("asciidoc").resolve("site");
3633
assets("/*", site);
3734

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

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

55-
runApp(args, DocApp::new);
52+
Server server = Server.loadServer();
53+
server.setOptions(new ServerOptions().setPort(4000));
54+
55+
runApp(args, server, DocApp::new);
5656

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

jooby/src/main/java/io/jooby/problem/HttpProblem.java

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,136 @@ private static String createMessage(String title, String detail) {
5555
return Objects.isNull(detail) ? title : title + ": " + detail;
5656
}
5757

58+
/**
59+
* Creates an HttpProblem with the specified status code and custom title.
60+
*
61+
* @param status the HTTP status code for this problem
62+
* @param title the title describing the type of problem
63+
* @return an HttpProblem instance with the specified status and title
64+
*/
5865
public static HttpProblem valueOf(StatusCode status, String title) {
5966
return builder().title(title).status(status).build();
6067
}
6168

69+
/**
70+
* Creates an HttpProblem with the specified status code, custom title, and detail.
71+
*
72+
* @param status the HTTP status code for this problem
73+
* @param title the title describing the type of problem
74+
* @param detail a human-readable explanation specific to this occurrence of the problem
75+
* @return an HttpProblem instance with the specified status, title, and detail
76+
*/
6277
public static HttpProblem valueOf(StatusCode status, String title, String detail) {
6378
return builder().title(title).status(status).detail(detail).build();
6479
}
6580

81+
/**
82+
* Creates an HttpProblem with the specified status code using the default title.
83+
* The title is automatically set to the status code's reason phrase.
84+
*
85+
* @param status the HTTP status code for this problem
86+
* @return an HttpProblem instance with the specified status and default title
87+
*/
6688
public static HttpProblem valueOf(StatusCode status) {
6789
return builder().title(status.reason()).status(status).build();
6890
}
6991

92+
/**
93+
* Creates an HttpProblem with BAD_REQUEST status (400) and custom title and detail.
94+
*
95+
* @param title the title describing the type of problem
96+
* @param detail a human-readable explanation specific to this occurrence of the problem
97+
* @return an HttpProblem instance with BAD_REQUEST status
98+
*/
99+
public static HttpProblem badRequest(String title, String detail) {
100+
return builder()
101+
.title(title)
102+
.status(StatusCode.BAD_REQUEST)
103+
.detail(detail)
104+
.build();
105+
}
106+
107+
/**
108+
* Creates an HttpProblem with BAD_REQUEST status (400) using the default title.
109+
* The title is automatically set to the status code's reason phrase.
110+
*
111+
* @param detail a human-readable explanation specific to this occurrence of the problem
112+
* @return an HttpProblem instance with BAD_REQUEST status
113+
*/
114+
public static HttpProblem badRequest(String detail) {
115+
return builder()
116+
.title(StatusCode.BAD_REQUEST.reason())
117+
.status(StatusCode.BAD_REQUEST)
118+
.detail(detail)
119+
.build();
120+
}
121+
122+
/**
123+
* Creates an HttpProblem with NOT_FOUND status (404) and custom title and detail.
124+
*
125+
* @param title the title describing the type of problem
126+
* @param detail a human-readable explanation specific to this occurrence of the problem
127+
* @return an HttpProblem instance with NOT_FOUND status
128+
*/
129+
public static HttpProblem notFound(String title, String detail) {
130+
return builder()
131+
.title(title)
132+
.status(StatusCode.NOT_FOUND)
133+
.detail(detail)
134+
.build();
135+
}
136+
137+
/**
138+
* Creates an HttpProblem with NOT_FOUND status (404) using the default title.
139+
* The title is automatically set to the status code's reason phrase.
140+
*
141+
* @param detail a human-readable explanation specific to this occurrence of the problem
142+
* @return an HttpProblem instance with NOT_FOUND status
143+
*/
144+
public static HttpProblem notFound(String detail) {
145+
return builder()
146+
.title(StatusCode.NOT_FOUND.reason())
147+
.status(StatusCode.NOT_FOUND)
148+
.detail(detail)
149+
.build();
150+
}
151+
152+
/**
153+
* Creates an HttpProblem with UNPROCESSABLE_ENTITY status (422) and custom title and detail.
154+
*
155+
* @param title the title describing the type of problem
156+
* @param detail a human-readable explanation specific to this occurrence of the problem
157+
* @return an HttpProblem instance with UNPROCESSABLE_ENTITY status
158+
*/
159+
public static HttpProblem unprocessableEntity(String title, String detail) {
160+
return builder()
161+
.title(title)
162+
.status(StatusCode.UNPROCESSABLE_ENTITY)
163+
.detail(detail)
164+
.build();
165+
}
166+
167+
/**
168+
* Creates an HttpProblem with UNPROCESSABLE_ENTITY status (422) using the default title.
169+
* The title is automatically set to the status code's reason phrase.
170+
*
171+
* @param detail a human-readable explanation specific to this occurrence of the problem
172+
* @return an HttpProblem instance with UNPROCESSABLE_ENTITY status
173+
*/
174+
public static HttpProblem unprocessableEntity(String detail) {
175+
return builder()
176+
.title(StatusCode.UNPROCESSABLE_ENTITY.reason())
177+
.status(StatusCode.UNPROCESSABLE_ENTITY)
178+
.detail(detail)
179+
.build();
180+
}
181+
182+
/**
183+
* Creates an HttpProblem with SERVER_ERROR status (500) using predefined title and detail.
184+
* This is typically used for unexpected server errors or misconfigurations.
185+
*
186+
* @return an HttpProblem instance with SERVER_ERROR status and predefined error message
187+
*/
70188
public static HttpProblem internalServerError() {
71189
return builder()
72190
.title(INTERNAL_ERROR_TITLE)
@@ -199,7 +317,8 @@ public static class Builder {
199317
private final Map<String, Object> headers = new LinkedHashMap<>();
200318
private final List<Error> errors = new LinkedList<>();
201319

202-
Builder() {}
320+
Builder() {
321+
}
203322

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

244363
/**
245-
* @param key additional info parameter name
364+
* @param key additional info parameter name
246365
* @param value additional info parameter value
247366
* @return this for chaining
248367
*/
@@ -269,19 +388,42 @@ public HttpProblem build() {
269388
}
270389
}
271390

391+
/**
392+
* Represents an individual error within an HTTP Problem response.
393+
* This class encapsulates error details following RFC 7807/RFC 9457 standards
394+
* for providing structured error information in API responses.
395+
*/
272396
public static class Error {
273397
private final String detail;
274398
private final String pointer;
275399

400+
/**
401+
* Creates a new Error instance with the specified detail and pointer.
402+
*
403+
* @param detail a human-readable explanation of the specific error
404+
* @param pointer a JSON Pointer (RFC 6901) that identifies the location
405+
* in the request document where the error occurred
406+
*/
276407
public Error(String detail, String pointer) {
277408
this.detail = detail;
278409
this.pointer = pointer;
279410
}
280411

412+
/**
413+
* Returns the human-readable explanation of this specific error.
414+
*
415+
* @return the error detail message
416+
*/
281417
public String getDetail() {
282418
return detail;
283419
}
284420

421+
/**
422+
* Returns a reference that identifies the location where this error occurred.
423+
* This is typically a JSON Pointer (RFC 6901).
424+
*
425+
* @return the pointer to the location of the error
426+
*/
285427
public String getPointer() {
286428
return pointer;
287429
}

0 commit comments

Comments
 (0)