You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/07/29 17:11:22 UTC

(juneau) 04/19: feat: add JsonRpcResponse.notification/ok/errorResponse static helpers

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 <ja...@apache.org>
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 <cu...@cursor.com>
---
 .../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());
+	}
+}