Skip to content

Commit eaee4fa

Browse files
committed
feat(jsonrpc): implement spec-compliant error handling and mapping
- Add `JsonRpcErrorCode` enum mapping core JSON-RPC 2.0 protocol errors (-32700 to -32603) and implementation-defined server errors (-32000 to -32099) to Jooby's standard HTTP `StatusCode`s. - Introduce a `protocol` flag on error codes to cleanly differentiate between transport/parsing faults and application-level exceptions. - Ensure error payloads comply with the 2.0 spec by utilizing the `data` field to hold specific exception messages, keeping the `message` field generic and strictly compliant. - Refactor and expand `JsonRpcProtocolTest` into isolated test methods. - Add comprehensive integration tests for error edge cases: missing/invalid parameters, unknown methods, application exception bubbling, and specific batch processing edge cases (empty arrays, arrays of invalid data).
1 parent 3df4e4e commit eaee4fa

File tree

18 files changed

+796
-428
lines changed

18 files changed

+796
-428
lines changed

jooby/src/main/java/io/jooby/Jooby.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import io.jooby.internal.MutedServer;
3636
import io.jooby.internal.RegistryRef;
3737
import io.jooby.internal.RouterImpl;
38-
import io.jooby.jsonrpc.JsonRpcDispatcher;
38+
import io.jooby.jsonrpc.JsonRpcModule;
3939
import io.jooby.jsonrpc.JsonRpcService;
4040
import io.jooby.output.OutputFactory;
4141
import io.jooby.problem.ProblemDetailsHandler;
@@ -104,7 +104,7 @@ public class Jooby implements Router, Registry {
104104

105105
private List<Locale> locales;
106106

107-
private Map<String, JsonRpcDispatcher> dispatchers;
107+
private Map<String, JsonRpcModule> dispatchers;
108108

109109
private boolean lateInit;
110110

@@ -430,7 +430,7 @@ public Jooby install(
430430
* @param factory Application factory.
431431
* @return This application.
432432
*/
433-
@NonNull public Jooby install(
433+
public Jooby install(
434434
@NonNull Predicate<Context> predicate, @NonNull SneakyThrows.Supplier<Jooby> factory) {
435435
return install("/", predicate, factory);
436436
}
@@ -501,7 +501,7 @@ public Jooby jsonRpc(String path, @NonNull JsonRpcService service) {
501501
.computeIfAbsent(
502502
Router.normalizePath(path),
503503
normalizedPath -> {
504-
var dispatcher = new JsonRpcDispatcher(normalizedPath);
504+
var dispatcher = new JsonRpcModule(normalizedPath);
505505
install(dispatcher);
506506
return dispatcher;
507507
})

jooby/src/main/java/io/jooby/jsonrpc/JsonRpcDispatcher.java

Lines changed: 0 additions & 141 deletions
This file was deleted.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.jsonrpc;
7+
8+
import io.jooby.StatusCode;
9+
10+
/**
11+
* Standard JSON-RPC 2.0 Error Codes mapped to HTTP status codes.
12+
*
13+
* <p>The JSON-RPC 2.0 specification defines a specific set of integer codes for standard errors.
14+
* This enumeration provides the canonical mapping between those JSON-RPC errors and Jooby's {@link
15+
* StatusCode} for HTTP transport bindings.
16+
*/
17+
public enum JsonRpcErrorCode {
18+
19+
// --- Core JSON-RPC 2.0 Errors ---
20+
21+
/** The JSON sent is not a valid Request object. */
22+
INVALID_REQUEST(-32600, "Invalid Request", StatusCode.BAD_REQUEST, true),
23+
24+
/**
25+
* Invalid JSON was received by the server. An error occurred on the server while parsing the JSON
26+
* text.
27+
*/
28+
PARSE_ERROR(-32700, "Parse error", StatusCode.BAD_REQUEST, true),
29+
30+
/** The method does not exist / is not available. */
31+
METHOD_NOT_FOUND(-32601, "Method not found", StatusCode.NOT_FOUND, true),
32+
33+
/** Invalid method parameter(s). */
34+
INVALID_PARAMS(-32602, "Invalid params", StatusCode.BAD_REQUEST, true),
35+
36+
/** Internal JSON-RPC error. */
37+
INTERNAL_ERROR(-32603, "Internal error", StatusCode.SERVER_ERROR, true),
38+
39+
// --- Implementation-defined Server Errors (-32000 to -32099) ---
40+
41+
/** Missing or invalid authentication. */
42+
UNAUTHORIZED(-32001, "Unauthorized", StatusCode.UNAUTHORIZED, false),
43+
44+
/** Authenticated user lacks required permissions. */
45+
FORBIDDEN(-32003, "Forbidden", StatusCode.FORBIDDEN, false),
46+
47+
/** The requested resource or procedure was not found (Business Logic). */
48+
NOT_FOUND_ERROR(-32004, "Not found", StatusCode.NOT_FOUND, false),
49+
50+
/** State conflict, such as a duplicate database entry. */
51+
CONFLICT(-32009, "Conflict", StatusCode.CONFLICT, false),
52+
53+
/** The client's preconditions were not met. */
54+
PRECONDITION_FAILED(-32012, "Precondition failed", StatusCode.PRECONDITION_FAILED, false),
55+
56+
/**
57+
* The payload format is valid, but the content is semantically incorrect (e.g., validation
58+
* failed).
59+
*/
60+
UNPROCESSABLE_CONTENT(-32022, "Unprocessable content", StatusCode.UNPROCESSABLE_ENTITY, false),
61+
62+
/** Rate limiting applied. */
63+
TOO_MANY_REQUESTS(-32029, "Too many requests", StatusCode.TOO_MANY_REQUESTS, false);
64+
65+
private final int code;
66+
private final String message;
67+
private final StatusCode statusCode;
68+
private final boolean protocol;
69+
70+
/**
71+
* Defines a JSON-RPC error code mapping.
72+
*
73+
* @param code The JSON-RPC 2.0 integer code.
74+
* @param message The standard error message.
75+
* @param statusCode The HTTP status code to associate with this error.
76+
* @param protocol True if this is a strict JSON-RPC 2.0 protocol error, false if
77+
* implementation-defined.
78+
*/
79+
JsonRpcErrorCode(int code, String message, StatusCode statusCode, boolean protocol) {
80+
this.code = code;
81+
this.message = message;
82+
this.statusCode = statusCode;
83+
this.protocol = protocol;
84+
}
85+
86+
/**
87+
* Retrieves the JSON-RPC integer code.
88+
*
89+
* @return The integer code (e.g., -32600).
90+
*/
91+
public int getCode() {
92+
return code;
93+
}
94+
95+
/**
96+
* Retrieves the standard JSON-RPC error message.
97+
*
98+
* @return The error message.
99+
*/
100+
public String getMessage() {
101+
return message;
102+
}
103+
104+
/**
105+
* Retrieves the corresponding HTTP status code.
106+
*
107+
* @return The Jooby {@link StatusCode}.
108+
*/
109+
public StatusCode getStatusCode() {
110+
return statusCode;
111+
}
112+
113+
/**
114+
* Indicates if this error is a core JSON-RPC 2.0 protocol error.
115+
*
116+
* @return True for strict protocol errors (-32700 to -32603), false for implementation-defined
117+
* errors.
118+
*/
119+
public boolean isProtocol() {
120+
return protocol;
121+
}
122+
123+
/**
124+
* Resolves the closest JSON-RPC error code for a given Jooby HTTP status code.
125+
*
126+
* <p>If an exact match is not found for the provided HTTP status, this method falls back to
127+
* {@link #INTERNAL_ERROR}.
128+
*
129+
* @param status The Jooby HTTP status code.
130+
* @return The corresponding {@code JsonRpcErrorCode}, or {@code INTERNAL_ERROR} if no match
131+
* exists.
132+
*/
133+
public static JsonRpcErrorCode of(StatusCode status) {
134+
for (var errorCode : values()) {
135+
if (!errorCode.protocol && errorCode.statusCode.value() == status.value()) {
136+
return errorCode;
137+
}
138+
}
139+
return INTERNAL_ERROR;
140+
}
141+
}

jooby/src/main/java/io/jooby/jsonrpc/JsonRpcException.java

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,8 @@
1212
* should be transformed into a {@link JsonRpcResponse} containing the error details.
1313
*/
1414
public class JsonRpcException extends RuntimeException {
15+
private final JsonRpcErrorCode code;
1516

16-
/**
17-
* Invalid JSON was received by the server. An error occurred on the server while parsing the JSON
18-
* text.
19-
*/
20-
public static final int PARSE_ERROR = -32700;
21-
22-
/** The JSON sent is not a valid Request object. */
23-
public static final int INVALID_REQUEST = -32600;
24-
25-
/** The method does not exist / is not available. */
26-
public static final int METHOD_NOT_FOUND = -32601;
27-
28-
/** Invalid method parameter(s). */
29-
public static final int INVALID_PARAMS = -32602;
30-
31-
/** Internal JSON-RPC error. */
32-
public static final int INTERNAL_ERROR = -32603;
33-
34-
private final int code;
3517
private final Object data;
3618

3719
/**
@@ -40,20 +22,33 @@ public class JsonRpcException extends RuntimeException {
4022
* @param code The integer error code (preferably one of the standard constants).
4123
* @param message A short description of the error.
4224
*/
43-
public JsonRpcException(int code, String message) {
25+
public JsonRpcException(JsonRpcErrorCode code, String message) {
4426
super(message);
4527
this.code = code;
4628
this.data = null;
4729
}
4830

31+
/**
32+
* Constructs a new JSON-RPC exception.
33+
*
34+
* @param code The integer error code (preferably one of the standard constants).
35+
* @param message A short description of the error.
36+
* @param cause The underlying cause of the error.
37+
*/
38+
public JsonRpcException(JsonRpcErrorCode code, String message, Throwable cause) {
39+
super(message, cause);
40+
this.code = code;
41+
this.data = null;
42+
}
43+
4944
/**
5045
* Constructs a new JSON-RPC exception with additional error data.
5146
*
5247
* @param code The integer error code.
5348
* @param message A short description of the error.
5449
* @param data Additional data about the error (e.g., stack trace or validation messages).
5550
*/
56-
public JsonRpcException(int code, String message, Object data) {
51+
public JsonRpcException(JsonRpcErrorCode code, String message, Object data) {
5752
super(message);
5853
this.code = code;
5954
this.data = data;
@@ -64,7 +59,7 @@ public JsonRpcException(int code, String message, Object data) {
6459
*
6560
* @return The JSON-RPC error code.
6661
*/
67-
public int getCode() {
62+
public JsonRpcErrorCode getCode() {
6863
return code;
6964
}
7065

0 commit comments

Comments
 (0)