Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.consensusj.jsonrpc.DefaultRpcClient;
import org.consensusj.jsonrpc.JsonRpcError;
import org.consensusj.jsonrpc.JsonRpcStatusException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand All @@ -42,9 +43,17 @@ public class ApplicationTest {

URI endpoint;

DefaultRpcClient client;

@BeforeEach
void testSetup() {
endpoint = URI.create(server.getURI().toString());
client = new DefaultRpcClient(endpoint, "", "");
}

@AfterEach
void testTeardown() {
client.close();
}

@Test
Expand All @@ -60,10 +69,8 @@ void serverStarts() {
@Test
void echoMethod() throws IOException {
var testString = "Hello jrpc-echod!";
try (var client = new DefaultRpcClient(endpoint, "", "")) {
String result = (String) client.send("echo", testString);
assertEquals(testString, result);
}
String result = (String) client.send("echo", testString);
assertEquals(testString, result);
}

@Test
Expand All @@ -73,46 +80,55 @@ void echoMethodWrongNumberOfArgs() throws IOException {
var testString = "Hello jrpc-echod!";
JsonRpcStatusException exception =
assertThrows(JsonRpcStatusException.class, () -> {
try (var client = new DefaultRpcClient(endpoint, "", "")) {
client.send("echo", testString, testString);
}
client.send("echo", testString, testString);
});
assertTrue(Objects.requireNonNull(exception.getMessage()).startsWith(expectedErrorMessagePrefix));
assertEquals(expectedErrorCode, exception.jsonRpcCode);
}


@Test
void helpMethod() throws IOException {
var expectedResult = """
echo message
help
stop
""";
try (var client = new DefaultRpcClient(endpoint, "", "")) {
String result = (String) client.send("help");
assertEquals(expectedResult, result);
}
void helpForHelpMethod() throws IOException {
var expectedMethodNameSubstring = "help <method>";
var expectedParamNameSubstring = "method (string, optional)";
var expectedDescriptiveTextSubstring = "Displays detailed help text for the specified method.";
String result = (String) client.send("help", "help");
assertTrue(result.contains(expectedMethodNameSubstring));
assertTrue(result.contains(expectedParamNameSubstring));
assertTrue(result.contains(expectedDescriptiveTextSubstring));

}

/*
* The help method is currently not fully implemented. It SHOULD allow
* for an argument, and only fail if the argument doesn't match an existing
* command. Once the help method is properly implemented we will need to change
* our tests
*/
@Test
void helpMethodOneArg() throws IOException {
int expectedErrorCode = JsonRpcError.Error.INVALID_PARAMS.getCode();
var expectedErrorMessagePrefix = "Invalid params:";
JsonRpcStatusException exception =
assertThrows(JsonRpcStatusException.class, () -> {
try (var client = new DefaultRpcClient(endpoint, "", "")) {
client.send("help", "echo");
}
});
assertTrue(Objects.requireNonNull(Objects.requireNonNull(exception.getMessage())).startsWith(expectedErrorMessagePrefix));
assertEquals(expectedErrorCode, exception.jsonRpcCode);
void helpForEchoMethod() throws IOException {
var expectedMethodNameSubstring = "echo <message>";
var expectedParamNameSubstring = "message (string, required)";
var expectedDescriptiveTextSubstring = "Returns the provided message exactly as it was sent.";
String result = (String) client.send("help", "echo");
assertTrue(result.contains(expectedMethodNameSubstring));
assertTrue(result.contains(expectedParamNameSubstring));
assertTrue(result.contains(expectedDescriptiveTextSubstring));

}

@Test
void helpForStopMethod() throws IOException {
var expectedMethodNameSubstring = "stop";
var expectedParamNameSubstring = "None.";
var expectedDescriptiveTextSubstring = "Initiates the shutdown process of the JSON-RPC server.";
String result = (String) client.send("help", "stop");
assertTrue(result.contains(expectedMethodNameSubstring));
assertTrue(result.contains(expectedParamNameSubstring));
assertTrue(result.contains(expectedDescriptiveTextSubstring));
}

@Test
void helpMethodNoArg() throws IOException {
Comment thread
liamgilligan marked this conversation as resolved.
var expectedResult = "echo message\n" +
"help (method)\n" +
"stop ";
String result = (String) client.send("help");
assertEquals(expectedResult, result);
}

@Test
Expand All @@ -121,9 +137,7 @@ void invalidMethod() throws IOException {
var expectedErrorMessagePrefix = "Method not found:";
JsonRpcStatusException exception =
assertThrows(JsonRpcStatusException.class, () -> {
try (var client = new DefaultRpcClient(endpoint, "", "")) {
client.send("invalid");
}
client.send("invalid");
});
assertTrue(Objects.requireNonNull(exception.getMessage()).startsWith(expectedErrorMessagePrefix));
assertEquals(expectedErrorCode, exception.jsonRpcCode);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2014-2026 ConsensusJ Developers.
*
* Licensed 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.consensusj.jsonrpc.help;

/**
* Recordlike object to store help information about a given command
*/
public class JsonRpcHelp {
Comment thread
liamgilligan marked this conversation as resolved.
private final String summary;
private final String detail;

public JsonRpcHelp(String summary, String detail) {
this.summary = summary;
this.detail = detail;
}

/**
* @return The summary information for a command.
*/
public String summary() {
return summary;
}

/**
* @return The detailed information for a command.
*/
public String detail() {
return detail;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,60 @@
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Map;
import org.consensusj.jsonrpc.help.JsonRpcHelp;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* Simple Echo JSON-RPC Service
*/
public class EchoJsonRpcService extends AbstractJsonRpcService implements Closeable {
private static final Logger log = LoggerFactory.getLogger(EchoJsonRpcService.class);
private static final Map<String, Method> methods = JsonRpcServiceWrapper.reflect(MethodHandles.lookup().lookupClass());
private static final String helpString =
"echo message\n" +
"help\n" +
"stop\n";
private static final Map<String, JsonRpcHelp> helpMap = Map.of(
Comment thread
liamgilligan marked this conversation as resolved.
"echo", new JsonRpcHelp("message",
"Usage:\n"
+ " echo <message>\n"
+ "\n"
+ "Description:\n"
+ " Returns the provided message exactly as it was sent.\n"
+ "\n"
+ "Parameters:\n"
+ " message (string, required)\n"
+ " The text to be echoed back by the server.\n"
+ "\n"
+ "Example:\n"
+ " echo \"hello world\"\n"),
"help", new JsonRpcHelp("(method)",
"Usage:\n"
+ " help <method>\n"
+ "\n"
+ "Description:\n"
+ " Displays detailed help text for the specified method.\n"
+ " If the method name is not recognized or not given, a list of available\n"
+ " methods and their parameters is returned instead.\n"
+ "\n"
+ "Parameters:\n"
+ " method (string, optional)\n"
+ " The name of the method to display help for.\n"
+ "\n"
+ "Example:\n"
+ " help echo\n"),
"stop", new JsonRpcHelp("",
"Usage:\n"
+ " stop\n"
+ "\n"
+ "Description:\n"
+ " Initiates the shutdown process of the JSON-RPC server.\n"
+ " The server will respond to this request before the\n"
+ " shutdown completes, allowing the client to receive\n"
+ " confirmation of the action.\n"
+ "\n"
+ "Parameters:\n"
+ " None.\n")
);
private static final String allMethodsHelpString = createAllMethodsHelpString(EchoJsonRpcService::createHelpStringLine);

private final JsonRpcShutdownService shutdownService;

Expand All @@ -50,14 +92,30 @@ public void close() {
log.info("Closing");
}

/**
* Echo a given message back to the client.
* @param message: A string containing the message to be echoed
* @return A string containing the echoed message
*/
public CompletableFuture<String> echo(String message) {
log.debug("EchoJsonRpcService: echo {}", message);
return result(message);
}

public CompletableFuture<String> help() {
/**
* Get detailed help information for a given command or a summary for all commands.
* @param method: A string containing the method name or null for all commands
* @return A string containing help information
*/
public CompletableFuture<String> help(String method) {
log.debug("EchoJsonRpcService: help");
return result(helpString);
if (method == null) {
return result(allMethodsHelpString);
} else if (helpMap.containsKey(method)) {
return result(helpMap.get(method).detail());
} else {
return result("Method not found.\n" + allMethodsHelpString);
}
}

/**
Expand All @@ -70,4 +128,26 @@ public CompletableFuture<String> stop() {
String message = shutdownService.stopServer();
return result(message);
}

/**
* Create a string containing the name of each method and its parameters in alphabetical order.
* @param formatFunction A function determining how to format each line
* @return A string containing the name and parameters of each method
*/
private static String createAllMethodsHelpString(Function<Map.Entry<String, JsonRpcHelp>, String> formatFunction) {
return helpMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(formatFunction)
.collect(Collectors.joining("\n"));
}

/**
* Default formatter. Appends the method's summary delimited with a space
* @param entry Map entry for a given method from `helpMap`
* @return Formatted line for `helpString`
*/
private static String createHelpStringLine(Map.Entry<String, JsonRpcHelp> entry) {
return entry.getKey() + " " + entry.getValue().summary();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ void testEcho() {

@Test
void testHelpSummary() {
String help = service.help(/* null */).join();
assertEquals("echo message\nhelp\nstop\n", help);
String help = service.help(null).join();
assertEquals("echo message\nhelp (method)\nstop ", help);
}

@Test
@Disabled
void testHelpDetail() {
String help = service.help(/* "help" */).join();
String help = service.help("help").join();
assertEquals("help help", help);
}
}
Loading