This is an automated email from the ASF dual-hosted git repository. jamesbognar pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/juneau.git
commit e09b9fe29ac5349047eb88fa13d98d1c4de1edda Author: James Bognar <[email protected]> AuthorDate: Tue Jul 28 16:14:55 2026 -0400 feat: add JsonRpcResponse.notification/ok/errorResponse static helpers Ports the neutral response-construction helpers out of McpDispatcher's privates onto the envelope bean itself. notification(Object) is a predicate, not a factory (matching the original private static); errorResponse keeps its 4-arg form so the internal-error path's error.data survives, with a 3-arg convenience overload. Co-authored-by: Cursor <[email protected]> --- .../juneau/bean/jsonrpc/JsonRpcResponse.java | 58 +++++++++++++ .../juneau/bean/jsonrpc/JsonRpcBeans_Test.java | 94 ++++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/juneau-bean/juneau-bean-jsonrpc/src/main/java/org/apache/juneau/bean/jsonrpc/JsonRpcResponse.java b/juneau-bean/juneau-bean-jsonrpc/src/main/java/org/apache/juneau/bean/jsonrpc/JsonRpcResponse.java index 0e4a38628c..6fdf4b2f1a 100644 --- a/juneau-bean/juneau-bean-jsonrpc/src/main/java/org/apache/juneau/bean/jsonrpc/JsonRpcResponse.java +++ b/juneau-bean/juneau-bean-jsonrpc/src/main/java/org/apache/juneau/bean/jsonrpc/JsonRpcResponse.java @@ -27,6 +27,8 @@ import org.apache.juneau.marshall.*; @Marshalled public class JsonRpcResponse { + private static final String JSONRPC_2_0 = "2.0"; + private String jsonrpc; private Object id; private Object result; @@ -111,4 +113,60 @@ public class JsonRpcResponse { error = value; return this; } + + /** + * Tests whether a JSON-RPC id identifies a notification (a request that must not be answered). + * + * <p> + * A JSON-RPC request with no {@code id} is a notification; the server performs the work and returns + * no response body. + * + * @param id The request id. Can be <jk>null</jk>. + * @return <jk>true</jk> if the id is <jk>null</jk>. + */ + public static boolean notification(Object id) { + return id == null; + } + + /** + * Creates a JSON-RPC 2.0 success response. + * + * @param id The request id to correlate against. Can be <jk>null</jk>. + * @param result The result payload. Can be <jk>null</jk>. + * @return A new response object. Never <jk>null</jk>. + */ + public static JsonRpcResponse ok(Object id, Object result) { + return new JsonRpcResponse() + .setJsonrpc(JSONRPC_2_0) + .setId(id) + .setResult(result); + } + + /** + * Creates a JSON-RPC 2.0 error response with no structured error data. + * + * @param id The request id to correlate against. Can be <jk>null</jk>. + * @param code The JSON-RPC error code. + * @param message The error message. Can be <jk>null</jk>. + * @return A new response object. Never <jk>null</jk>. + */ + public static JsonRpcResponse errorResponse(Object id, int code, String message) { + return errorResponse(id, code, message, null); + } + + /** + * Creates a JSON-RPC 2.0 error response. + * + * @param id The request id to correlate against. Can be <jk>null</jk>. + * @param code The JSON-RPC error code. + * @param message The error message. Can be <jk>null</jk>. + * @param data Optional structured error data. Can be <jk>null</jk> to leave the property unset. + * @return A new response object. Never <jk>null</jk>. + */ + public static JsonRpcResponse errorResponse(Object id, int code, String message, Object data) { + return new JsonRpcResponse() + .setJsonrpc(JSONRPC_2_0) + .setId(id) + .setError(new JsonRpcError().setCode(code).setMessage(message).setData(data)); + } } diff --git a/juneau-bean/juneau-bean-jsonrpc/src/test/java/org/apache/juneau/bean/jsonrpc/JsonRpcBeans_Test.java b/juneau-bean/juneau-bean-jsonrpc/src/test/java/org/apache/juneau/bean/jsonrpc/JsonRpcBeans_Test.java new file mode 100644 index 0000000000..2fe0997f13 --- /dev/null +++ b/juneau-bean/juneau-bean-jsonrpc/src/test/java/org/apache/juneau/bean/jsonrpc/JsonRpcBeans_Test.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.juneau.bean.jsonrpc; + +import static org.junit.jupiter.api.Assertions.*; + +import org.apache.juneau.marshall.collections.*; +import org.apache.juneau.marshall.json.*; +import org.junit.jupiter.api.*; + +/** + * Coverage for the JSON-RPC 2.0 envelope beans and {@link JsonRpcResponse}'s static helpers. + */ +class JsonRpcBeans_Test { + + private static void assertRoundTrip(Object bean, Class<?> type) { + var a = JsonSerializer.DEFAULT.write(bean); + var b = JsonSerializer.DEFAULT.write(JsonParser.DEFAULT.read(a, type)); + assertEquals(a, b, () -> "Round-trip JSON mismatch for " + type.getName() + ": " + a); + } + + @Test + void a01_request_roundTrip() { + var a = new JsonRpcRequest().setJsonrpc("2.0").setId("abc").setMethod("tools/list").setParams(JsonMap.of("cursor", "1")); + assertRoundTrip(a, JsonRpcRequest.class); + } + + @Test + void a02_response_resultRoundTrip() { + var a = new JsonRpcResponse().setJsonrpc("2.0").setId(42).setResult(JsonMap.of("x", 1)); + assertRoundTrip(a, JsonRpcResponse.class); + } + + @Test + void a03_response_errorRoundTrip() { + var a = new JsonRpcResponse().setJsonrpc("2.0").setId(7) + .setError(new JsonRpcError().setCode(-32600).setMessage("Invalid Request").setData(JsonMap.of("detail", "x"))); + assertRoundTrip(a, JsonRpcResponse.class); + } + + @Test + void a04_error_roundTrip() { + assertRoundTrip(new JsonRpcError().setCode(-32603).setMessage("boom").setData(JsonMap.of("type", "X")), JsonRpcError.class); + } + + @Test + void b01_notification_isNullId() { + assertTrue(JsonRpcResponse.notification(null)); + assertFalse(JsonRpcResponse.notification(1)); + assertFalse(JsonRpcResponse.notification("abc")); + } + + @Test + void b02_ok_setsVersionIdAndResult() { + var a = JsonRpcResponse.ok(1, JsonMap.of("k", "v")); + assertEquals("2.0", a.getJsonrpc()); + assertEquals(1, a.getId()); + assertNotNull(a.getResult()); + assertNull(a.getError()); + } + + @Test + void b03_errorResponse_fourArg_carriesData() { + var a = JsonRpcResponse.errorResponse(1, -32603, "boom", JsonMap.of("type", "X")); + assertEquals("2.0", a.getJsonrpc()); + assertEquals(1, a.getId()); + assertNull(a.getResult()); + assertEquals(-32603, a.getError().getCode()); + assertEquals("boom", a.getError().getMessage()); + assertNotNull(a.getError().getData()); + } + + @Test + void b04_errorResponse_threeArg_leavesDataNull() { + var a = JsonRpcResponse.errorResponse(null, -32600, "Request envelope is null"); + assertNull(a.getId()); + assertEquals(-32600, a.getError().getCode()); + assertNull(a.getError().getData()); + } +}
