1414import io .jooby .*;
1515import io .jooby .exception .MissingValueException ;
1616import io .jooby .exception .TypeMismatchException ;
17+ import io .jooby .internal .jsonrpc .JsonRpcExceptionTranslator ;
18+ import io .jooby .internal .jsonrpc .JsonRpcExecutor ;
19+ import io .jooby .jsonrpc .instrumentation .OtelJsonRcpTracing ;
1720
1821/**
1922 * Global Tier 1 Dispatcher for JSON-RPC 2.0 requests.
@@ -52,6 +55,8 @@ public class JsonRpcModule implements Extension {
5255 private final Map <String , JsonRpcService > services = new HashMap <>();
5356 private final String path ;
5457 private @ Nullable JsonRpcInvoker invoker ;
58+ private @ Nullable OtelJsonRcpTracing head ;
59+ private JsonRpcExceptionTranslator exceptionTranslator ;
5560
5661 public JsonRpcModule (String path , JsonRpcService service , JsonRpcService ... services ) {
5762 this .path = path ;
@@ -64,10 +69,15 @@ public JsonRpcModule(JsonRpcService service, JsonRpcService... services) {
6469 }
6570
6671 public JsonRpcModule invoker (JsonRpcInvoker invoker ) {
67- if (this .invoker != null ) {
68- this .invoker = invoker .then (this .invoker );
72+ if (invoker instanceof OtelJsonRcpTracing otel ) {
73+ // otel goes first:
74+ this .head = otel ;
6975 } else {
70- this .invoker = invoker ;
76+ if (this .invoker != null ) {
77+ this .invoker = invoker .then (this .invoker );
78+ } else {
79+ this .invoker = invoker ;
80+ }
7181 }
7282 return this ;
7383 }
@@ -86,8 +96,13 @@ private void registry(JsonRpcService service) {
8696 */
8797 @ Override
8898 public void install (Jooby app ) throws Exception {
99+ if (head != null ) {
100+ invoker = invoker == null ? head : head .then (invoker );
101+ }
89102 app .post (path , this ::handle );
90103
104+ exceptionTranslator = new JsonRpcExceptionTranslator (app );
105+ app .getServices ().put (JsonRpcExceptionTranslator .class , exceptionTranslator );
91106 // Initialize the custom exception mapping registry
92107 app .getServices ()
93108 .mapOf (Class .class , JsonRpcErrorCode .class )
@@ -106,64 +121,43 @@ public void install(Jooby app) throws Exception {
106121 * @return A single {@link JsonRpcResponse}, a {@code List} of responses for batches, or an empty
107122 * string for notifications.
108123 */
109- private Object handle (Context ctx ) {
124+ private Object handle (Context ctx ) throws Exception {
110125 JsonRpcRequest input ;
111126 try {
112127 input = ctx .body (JsonRpcRequest .class );
113- } catch (Exception e ) {
114- // Spec: -32700 Parse error if the JSON is physically malformed.
115- return JsonRpcResponse .error (null , JsonRpcErrorCode .PARSE_ERROR , e );
128+ } catch (Exception cause ) {
129+ var badRequest = new JsonRpcRequest ();
130+ badRequest .setMethod (JsonRpcRequest .UNKNOWN_METHOD );
131+ var parseError = JsonRpcResponse .error (null , JsonRpcErrorCode .PARSE_ERROR , cause );
132+ if (head != null ) {
133+ // Manually handle bad request for otel
134+ return head .invoke (ctx , badRequest , () -> Optional .of (parseError ));
135+ }
136+ log (badRequest , cause );
137+ return parseError ;
116138 }
117139
118140 List <JsonRpcResponse > responses = new ArrayList <>();
119141
120142 // Look up all generated *Rpc classes registered in the service registry
121-
122143 for (var request : input ) {
123- var fullMethod = request .getMethod ();
124-
125- // Spec: -32600 Invalid Request if the method member is missing or null
126- if (fullMethod == null ) {
127- responses .add (
128- JsonRpcResponse .error (request .getId (), JsonRpcErrorCode .INVALID_REQUEST , null ));
129- continue ;
130- }
131-
132144 try {
133- var targetService = services .get (fullMethod );
134- if (targetService != null ) {
135- var result =
136- invoker == null
137- ? targetService .execute (ctx , request )
138- : invoker .invoke (ctx , request , () -> targetService .execute (ctx , request ));
139- // Spec: If the "id" is missing, it is a notification and no response is returned.
140- if (request .getId () != null ) {
141- if (result instanceof JsonRpcResponse jsonRpcResponse ) {
142- responses .add (jsonRpcResponse );
143- } else {
144- responses .add (JsonRpcResponse .success (request .getId (), result ));
145- }
146- }
147- } else {
148- // Spec: -32601 Method not found
149- responses .add (
150- JsonRpcResponse .error (
151- request .getId (),
152- JsonRpcErrorCode .METHOD_NOT_FOUND ,
153- "Method not found: " + fullMethod ));
154- }
145+ var target = new JsonRpcExecutor (services , ctx , request );
146+ var response = invoker == null ? target .get () : invoker .invoke (ctx , request , target );
147+ response .ifPresent (responses ::add );
155148 } catch (JsonRpcException cause ) {
156- log (ctx , request , cause );
149+ log (request , cause );
157150 // Domain-specific or protocol-level exceptions (e.g., -32602 Invalid Params)
158151 if (request .getId () != null ) {
159152 responses .add (JsonRpcResponse .error (request .getId (), cause .getCode (), cause .getCause ()));
160153 }
161154 } catch (Exception cause ) {
162- log (ctx , request , cause );
155+ log (request , cause );
163156 // Spec: -32603 Internal error for unhandled application exceptions
164157 if (request .getId () != null ) {
165158 responses .add (
166- JsonRpcResponse .error (request .getId (), computeErrorCode (ctx , cause ), cause ));
159+ JsonRpcResponse .error (
160+ request .getId (), exceptionTranslator .toErrorCode (cause ), cause ));
167161 }
168162 }
169163 }
@@ -178,14 +172,14 @@ private Object handle(Context ctx) {
178172 return input .isBatch () ? responses : responses .getFirst ();
179173 }
180174
181- private void log (Context ctx , JsonRpcRequest request , Throwable cause ) {
175+ private void log (JsonRpcRequest request , Throwable cause ) {
182176 JsonRpcErrorCode code ;
183177 boolean hasCause = true ;
184178 if (cause instanceof JsonRpcException rpcException ) {
185179 code = rpcException .getCode ();
186180 hasCause = false ;
187181 } else {
188- code = computeErrorCode ( ctx , cause );
182+ code = exceptionTranslator . toErrorCode ( cause );
189183 }
190184 var type = code == JsonRpcErrorCode .INTERNAL_ERROR ? "server" : "client" ;
191185 var message = "JSON-RPC {} error [{} {}] on method '{}' (id: {})" ;
@@ -230,33 +224,4 @@ private void log(Context ctx, JsonRpcRequest request, Throwable cause) {
230224 }
231225 }
232226 }
233-
234- private JsonRpcErrorCode computeErrorCode (Context ctx , Throwable cause ) {
235- JsonRpcErrorCode code ;
236- // Attempt to look up any user-defined exception mappings from the registry
237- Map <Class <?>, JsonRpcErrorCode > customMapping =
238- ctx .require (Reified .map (Class .class , JsonRpcErrorCode .class ));
239- code =
240- errorCode (customMapping , cause )
241- .orElseGet (() -> JsonRpcErrorCode .of (ctx .getRouter ().errorCode (cause )));
242- return code ;
243- }
244-
245- /**
246- * Evaluates the given exception against the registered custom exception mappings.
247- *
248- * @param mappings A map of Exception classes to specific tRPC error codes.
249- * @param x The exception to evaluate.
250- * @return An {@code Optional} containing the matched {@code TrpcErrorCode}, or empty if no match
251- * is found.
252- */
253- private Optional <JsonRpcErrorCode > errorCode (
254- Map <Class <?>, JsonRpcErrorCode > mappings , Throwable x ) {
255- for (var mapping : mappings .entrySet ()) {
256- if (mapping .getKey ().isInstance (x )) {
257- return Optional .of (mapping .getValue ());
258- }
259- }
260- return Optional .empty ();
261- }
262227}
0 commit comments