From 32ea81263e00f5d702e81730a42938685f9a2165 Mon Sep 17 00:00:00 2001 From: PJ Doerner Date: Mon, 20 Oct 2025 20:33:26 -0700 Subject: [PATCH] Remove FetchInfo and FetchResult --- README.md | 4 +- .../main/java/io/nexusrpc/OperationInfo.java | 86 --------------- .../OperationStillRunningException.java | 8 -- .../java/io/nexusrpc/handler/Handler.java | 22 ---- .../handler/OperationFetchInfoDetails.java | 71 ------------- .../handler/OperationFetchResultDetails.java | 100 ------------------ .../io/nexusrpc/handler/OperationHandler.java | 37 ------- .../io/nexusrpc/handler/ServiceHandler.java | 42 -------- .../handler/SynchronousOperationHandler.java | 11 -- .../nexusrpc/example/GreetingServiceImpl.java | 50 --------- .../nexusrpc/handler/ServiceHandlerTest.java | 54 +--------- 11 files changed, 6 insertions(+), 479 deletions(-) delete mode 100644 nexus-sdk/src/main/java/io/nexusrpc/OperationInfo.java delete mode 100644 nexus-sdk/src/main/java/io/nexusrpc/OperationStillRunningException.java delete mode 100644 nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchInfoDetails.java delete mode 100644 nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchResultDetails.java diff --git a/README.md b/README.md index a55ab8f..8394e59 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Java SDK for working with [Nexus RPC](https://github.com/nexus-rpc/api). Nexus is a synchronous RPC protocol. Arbitrary length operations are modelled on top of a set of pre-defined synchronous RPCs. A Nexus caller calls a handler. The handler may respond inline or return a reference for a future, asynchronous -operation. The caller can cancel an asynchronous operation, check for its outcome, or fetch its current state. The -caller can also specify a callback URL, which the handler uses to asynchronously deliver the result of an operation when it is ready. +operation. The caller can cancel an asynchronous operation using the returned token. The caller can also specify a +callback URL, which the handler uses to asynchronously deliver the result of an operation when it is ready. ## Supported Java runtimes * Java 1.8+ diff --git a/nexus-sdk/src/main/java/io/nexusrpc/OperationInfo.java b/nexus-sdk/src/main/java/io/nexusrpc/OperationInfo.java deleted file mode 100644 index 20c0963..0000000 --- a/nexus-sdk/src/main/java/io/nexusrpc/OperationInfo.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.nexusrpc; - -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** Information about an operation. */ -@Experimental -public class OperationInfo { - /** Create a builder. */ - public static Builder newBuilder() { - return new Builder(); - } - - /** Create a builder from existing info. */ - public static Builder newBuilder(OperationInfo info) { - return new Builder(info); - } - - private final String token; - private final OperationState state; - - private OperationInfo(String token, OperationState state) { - this.token = token; - this.state = state; - } - - /** State of the operation. */ - public OperationState getState() { - return state; - } - - /** Token of the operation. */ - public String getToken() { - return token; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - OperationInfo that = (OperationInfo) o; - return Objects.equals(token, that.token) && state == that.state; - } - - @Override - public int hashCode() { - return Objects.hash(token, state); - } - - @Override - public String toString() { - return "OperationInfo{" + "token='" + token + '\'' + ", state=" + state + '}'; - } - - /** Builder for operation info. */ - public static class Builder { - @Nullable private String token; - @Nullable private OperationState state; - - private Builder() {} - - private Builder(OperationInfo info) { - token = info.token; - state = info.state; - } - - /** Set operation token. Required. */ - public Builder setToken(String token) { - this.token = token; - return this; - } - - /** Set operation state. Required. */ - public Builder setState(OperationState state) { - this.state = state; - return this; - } - - /** Build the info. */ - public OperationInfo build() { - Objects.requireNonNull(token, "Operation Token required"); - Objects.requireNonNull(state, "State required"); - return new OperationInfo(token, state); - } - } -} diff --git a/nexus-sdk/src/main/java/io/nexusrpc/OperationStillRunningException.java b/nexus-sdk/src/main/java/io/nexusrpc/OperationStillRunningException.java deleted file mode 100644 index cf13106..0000000 --- a/nexus-sdk/src/main/java/io/nexusrpc/OperationStillRunningException.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.nexusrpc; - -/** An operation result was requested, but it is still running. */ -public class OperationStillRunningException extends Exception { - public OperationStillRunningException() { - super("Operation still running"); - } -} diff --git a/nexus-sdk/src/main/java/io/nexusrpc/handler/Handler.java b/nexus-sdk/src/main/java/io/nexusrpc/handler/Handler.java index 041fb60..d35dad7 100644 --- a/nexus-sdk/src/main/java/io/nexusrpc/handler/Handler.java +++ b/nexus-sdk/src/main/java/io/nexusrpc/handler/Handler.java @@ -1,9 +1,6 @@ package io.nexusrpc.handler; -import io.nexusrpc.Experimental; import io.nexusrpc.OperationException; -import io.nexusrpc.OperationInfo; -import io.nexusrpc.OperationStillRunningException; /** Top-level handler for service calls. */ public interface Handler { @@ -18,25 +15,6 @@ OperationStartResult startOperation( OperationContext context, OperationStartDetails details, HandlerInputContent input) throws OperationException, HandlerException; - /** - * Fetch the result for an asynchronously started operation. See {@link - * OperationHandler#fetchResult} for operation details. - * - *

If the result is an output stream, it will be closed later by the caller. - */ - @Experimental - HandlerResultContent fetchOperationResult( - OperationContext context, OperationFetchResultDetails details) - throws OperationStillRunningException, OperationException, HandlerException; - - /** - * Fetch information about the asynchronously started operation. See {@link - * OperationHandler#fetchInfo} for details. - */ - @Experimental - OperationInfo fetchOperationInfo(OperationContext context, OperationFetchInfoDetails details) - throws HandlerException; - /** * Cancel the asynchronously started operation. See {@link OperationHandler#cancel} for details. */ diff --git a/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchInfoDetails.java b/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchInfoDetails.java deleted file mode 100644 index f0703ab..0000000 --- a/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchInfoDetails.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.nexusrpc.handler; - -import io.nexusrpc.Experimental; -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** Details for handling operation fetch info. */ -@Experimental -public class OperationFetchInfoDetails { - /** Create a builder. */ - public static Builder newBuilder() { - return new Builder(); - } - - /** Create a builder from an existing set of details. */ - public static Builder newBuilder(OperationFetchInfoDetails details) { - return new Builder(details); - } - - private final String operationToken; - - private OperationFetchInfoDetails(String operationToken) { - this.operationToken = operationToken; - } - - /** ID of the operation. */ - public String getOperationToken() { - return operationToken; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - OperationFetchInfoDetails that = (OperationFetchInfoDetails) o; - return Objects.equals(operationToken, that.operationToken); - } - - @Override - public int hashCode() { - return Objects.hashCode(operationToken); - } - - @Override - public String toString() { - return "OperationFetchInfoDetails{" + "operationToken='" + operationToken + '\'' + '}'; - } - - /** Builder for operation fetch info details. */ - public static class Builder { - private @Nullable String operationToken; - - private Builder() {} - - private Builder(OperationFetchInfoDetails details) { - operationToken = details.operationToken; - } - - /** Set operation token. Required. */ - public Builder setOperationToken(String operationToken) { - this.operationToken = operationToken; - return this; - } - - /** Build the details. */ - public OperationFetchInfoDetails build() { - Objects.requireNonNull(operationToken, "Operation Token required"); - return new OperationFetchInfoDetails(operationToken); - } - } -} diff --git a/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchResultDetails.java b/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchResultDetails.java deleted file mode 100644 index 2aab5a6..0000000 --- a/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationFetchResultDetails.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.nexusrpc.handler; - -import io.nexusrpc.Experimental; -import io.nexusrpc.OperationStillRunningException; -import java.time.Duration; -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** Details for handling operation fetch result. */ -@Experimental -public class OperationFetchResultDetails { - /** Create a builder. */ - public static Builder newBuilder() { - return new Builder(); - } - - /** Create a builder from an existing set of details. */ - public static Builder newBuilder(OperationFetchResultDetails details) { - return new Builder(details); - } - - private final String operationToken; - private final @Nullable Duration timeout; - - private OperationFetchResultDetails(String operationToken, @Nullable Duration timeout) { - this.operationToken = operationToken; - this.timeout = timeout; - } - - /** ID of the operation. */ - public String getOperationToken() { - return operationToken; - } - - /** - * Optional timeout for how long the user wants to wait on the result. - * - *

If this value is null, the result or {@link OperationStillRunningException} should be - * returned/thrown right away. If this value is present, the fetch result call should try to wait - * up until this duration or until an implementer chosen maximum, whichever ends sooner, before - * returning the result or throwing {@link OperationStillRunningException}. - */ - public @Nullable Duration getTimeout() { - return timeout; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - OperationFetchResultDetails that = (OperationFetchResultDetails) o; - return Objects.equals(operationToken, that.operationToken) - && Objects.equals(timeout, that.timeout); - } - - @Override - public int hashCode() { - return Objects.hash(operationToken, timeout); - } - - @Override - public String toString() { - return "OperationFetchResultDetails{" - + "operationToken='" - + operationToken - + '\'' - + ", timeout=" - + timeout - + '}'; - } - - /** Builder for operation fetch result details. */ - public static class Builder { - private @Nullable String operationToken; - private @Nullable Duration timeout; - - private Builder() {} - - private Builder(OperationFetchResultDetails details) { - operationToken = details.operationToken; - } - - /** Set operation token. Required. */ - public Builder setOperationToken(String operationToken) { - this.operationToken = operationToken; - return this; - } - - /** Set timeout. */ - public Builder setTimeout(Duration timeout) { - this.timeout = timeout; - return this; - } - - public OperationFetchResultDetails build() { - Objects.requireNonNull(operationToken, "Operation Token required"); - return new OperationFetchResultDetails(operationToken, timeout); - } - } -} diff --git a/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationHandler.java b/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationHandler.java index 2229446..687ca62 100644 --- a/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationHandler.java +++ b/nexus-sdk/src/main/java/io/nexusrpc/handler/OperationHandler.java @@ -1,8 +1,6 @@ package io.nexusrpc.handler; import io.nexusrpc.OperationException; -import io.nexusrpc.OperationInfo; -import io.nexusrpc.OperationStillRunningException; import org.jspecify.annotations.Nullable; /** @@ -47,41 +45,6 @@ OperationStartResult start( OperationContext context, OperationStartDetails details, @Nullable T param) throws OperationException, HandlerException; - /** - * Fetch the result for an asynchronously started operation. - * - * @param context Context for the call. - * @param details Details for the call including the operation token. The details also contain a - * timeout which affects how implementers should implement this function. See {@link - * OperationFetchResultDetails#getTimeout()} to see how to react to this value. - * @return The resulting value upon success. - * @throws OperationStillRunningException Operation is still running beyond the given timeout. - * @throws OperationException Operation failed. If thrown, can have failure details and state such - * as saying the operation was cancelled. - * @throws HandlerException Unexpected failures while running the handler. This should be thrown - * with a type of {@link HandlerException.ErrorType#NOT_FOUND} if the operation token is not - * found. - * @throws RuntimeException Any other exception, will be converted to an {@link HandlerException} - * of type {@link HandlerException.ErrorType#INTERNAL}. - */ - @Nullable R fetchResult(OperationContext context, OperationFetchResultDetails details) - throws OperationStillRunningException, OperationException, HandlerException; - - /** - * Fetch information about the asynchronously started operation. - * - * @param context Context for the call. - * @param details Details for the call including the operation token. - * @return Information about the operation. - * @throws HandlerException Unexpected failures while running the handler. This should be thrown - * with a type of {@link HandlerException.ErrorType#NOT_FOUND} if the operation token is not - * found. - * @throws RuntimeException Any other exception, will be converted to an {@link HandlerException} - * of type {@link HandlerException.ErrorType#INTERNAL}. - */ - OperationInfo fetchInfo(OperationContext context, OperationFetchInfoDetails details) - throws HandlerException; - /** * Cancel the asynchronously started operation. * diff --git a/nexus-sdk/src/main/java/io/nexusrpc/handler/ServiceHandler.java b/nexus-sdk/src/main/java/io/nexusrpc/handler/ServiceHandler.java index 9f055ab..e9b8b32 100644 --- a/nexus-sdk/src/main/java/io/nexusrpc/handler/ServiceHandler.java +++ b/nexus-sdk/src/main/java/io/nexusrpc/handler/ServiceHandler.java @@ -100,29 +100,6 @@ public OperationStartResult startOperation( return OperationStartResult.sync(resultToContent(result.getSyncResult())); } - @Override - public HandlerResultContent fetchOperationResult( - OperationContext context, OperationFetchResultDetails details) - throws OperationStillRunningException, OperationException { - ServiceImplInstance instance = instances.get(context.getService()); - if (instance == null) { - throw newUnrecognizedOperationException(context.getService(), context.getOperation()); - } - OperationHandler handler = - instance.getOperationHandlers().get(context.getOperation()); - if (handler == null) { - throw newUnrecognizedOperationException(context.getService(), context.getOperation()); - } - // Populate the service definition in the context so that the handler can use it - OperationContext contextWithServiceDef = - OperationContext.newBuilder(context).setServiceDefinition(instance.getDefinition()).build(); - - Object result = - interceptOperationHandler(contextWithServiceDef, handler) - .fetchResult(contextWithServiceDef, details); - return resultToContent(result); - } - private HandlerResultContent resultToContent(Object result) { try { Serializer.Content output = serializer.serialize(result); @@ -135,25 +112,6 @@ private HandlerResultContent resultToContent(Object result) { } } - @Override - public OperationInfo fetchOperationInfo( - OperationContext context, OperationFetchInfoDetails details) { - ServiceImplInstance instance = instances.get(context.getService()); - if (instance == null) { - throw newUnrecognizedOperationException(context.getService(), context.getOperation()); - } - OperationHandler handler = - instance.getOperationHandlers().get(context.getOperation()); - if (handler == null) { - throw newUnrecognizedOperationException(context.getService(), context.getOperation()); - } - // Populate the service definition in the context so that the handler can use it - OperationContext contextWithServiceDef = - OperationContext.newBuilder(context).setServiceDefinition(instance.getDefinition()).build(); - return interceptOperationHandler(contextWithServiceDef, handler) - .fetchInfo(contextWithServiceDef, details); - } - @Override public void cancelOperation(OperationContext context, OperationCancelDetails details) { ServiceImplInstance instance = instances.get(context.getService()); diff --git a/nexus-sdk/src/main/java/io/nexusrpc/handler/SynchronousOperationHandler.java b/nexus-sdk/src/main/java/io/nexusrpc/handler/SynchronousOperationHandler.java index f2bc55f..997b2c2 100644 --- a/nexus-sdk/src/main/java/io/nexusrpc/handler/SynchronousOperationHandler.java +++ b/nexus-sdk/src/main/java/io/nexusrpc/handler/SynchronousOperationHandler.java @@ -1,7 +1,6 @@ package io.nexusrpc.handler; import io.nexusrpc.OperationException; -import io.nexusrpc.OperationInfo; import org.jspecify.annotations.Nullable; /** Handler for synchronous operation function. */ @@ -19,16 +18,6 @@ public OperationStartResult start( return OperationStartResult.sync(function.apply(context, details, param)); } - @Override - public @Nullable R fetchResult(OperationContext context, OperationFetchResultDetails details) { - throw new UnsupportedOperationException("Not supported on sync operation"); - } - - @Override - public OperationInfo fetchInfo(OperationContext context, OperationFetchInfoDetails details) { - throw new UnsupportedOperationException("Not supported on sync operation"); - } - @Override public void cancel(OperationContext context, OperationCancelDetails details) { throw new UnsupportedOperationException("Not supported on sync operation"); diff --git a/nexus-sdk/src/test/java/io/nexusrpc/example/GreetingServiceImpl.java b/nexus-sdk/src/test/java/io/nexusrpc/example/GreetingServiceImpl.java index 1905e39..70f6155 100644 --- a/nexus-sdk/src/test/java/io/nexusrpc/example/GreetingServiceImpl.java +++ b/nexus-sdk/src/test/java/io/nexusrpc/example/GreetingServiceImpl.java @@ -60,56 +60,6 @@ public OperationStartResult start( return OperationStartResult.async(id); } - @Override - public String fetchResult(OperationContext context, OperationFetchResultDetails details) - throws OperationStillRunningException { - Future operation = getOperation(details.getOperationToken()); - try { - // When timeout missing, be done or fail - if (details.getTimeout() == null) { - if (!operation.isDone()) { - throw new OperationStillRunningException(); - } - return operation.get(); - } - // User willing to wait - try { - return operation.get(details.getTimeout().toMillis(), TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - throw new OperationStillRunningException(); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } - throw new RuntimeException(e.getCause()); - } - } - - @Override - public OperationInfo fetchInfo(OperationContext context, OperationFetchInfoDetails details) { - Future operation = getOperation(details.getOperationToken()); - OperationState state; - if (operation.isCancelled()) { - state = OperationState.CANCELED; - } else if (!operation.isDone()) { - state = OperationState.RUNNING; - } else { - try { - operation.get(); - state = OperationState.SUCCEEDED; - } catch (Exception e) { - state = OperationState.FAILED; - } - } - return OperationInfo.newBuilder() - .setToken(details.getOperationToken()) - .setState(state) - .build(); - } - @Override public void cancel(OperationContext context, OperationCancelDetails details) { getOperation(details.getOperationToken()).cancel(true); diff --git a/nexus-sdk/src/test/java/io/nexusrpc/handler/ServiceHandlerTest.java b/nexus-sdk/src/test/java/io/nexusrpc/handler/ServiceHandlerTest.java index e21184c..4e7a402 100644 --- a/nexus-sdk/src/test/java/io/nexusrpc/handler/ServiceHandlerTest.java +++ b/nexus-sdk/src/test/java/io/nexusrpc/handler/ServiceHandlerTest.java @@ -163,7 +163,7 @@ void genericParameterService() { } @Test - void simpleGreetingService() throws OperationException, OperationStillRunningException { + void simpleGreetingService() throws OperationException { // Create API client AtomicReference apiClientInternal = new AtomicReference<>(); ApiClient apiClient = name -> apiClientInternal.get().createGreeting(name); @@ -199,44 +199,13 @@ void simpleGreetingService() throws OperationException, OperationStillRunningExc Objects.requireNonNull(Objects.requireNonNull(result.getSyncResult()).getDataBytes()), StandardCharsets.UTF_8)); - // Call handler form with regular name which uses asynchronous handling. First, have the API - // client wait. - AtomicReference> pendingFuture = new AtomicReference<>(); + // Test an async operation with a link apiClientInternal.set( name -> { assertTrue(name.startsWith("SomeUser")); - pendingFuture.set(new CompletableFuture<>()); - return pendingFuture.get(); + return new CompletableFuture<>(); }); - // Now call - result = - handler.startOperation( - newGreetingServiceContext("sayHello2"), - OperationStartDetails.newBuilder().setRequestId("request-id-3").build(), - newSimpleInputContent("SomeUser")); - String operationToken = Objects.requireNonNull(result.getAsyncOperationToken()); - // Confirm future is waiting and info says it's running - OperationInfo info = - handler.fetchOperationInfo( - newGreetingServiceContext("sayHello2"), - OperationFetchInfoDetails.newBuilder().setOperationToken(operationToken).build()); - assertEquals(OperationState.RUNNING, info.getState()); - // Resolve future and confirm succeeded - Objects.requireNonNull(pendingFuture.get()).complete("Hello from API, SomeUser!"); - info = - handler.fetchOperationInfo( - newGreetingServiceContext("sayHello2"), - OperationFetchInfoDetails.newBuilder().setOperationToken(operationToken).build()); - assertEquals(OperationState.SUCCEEDED, info.getState()); - // Check result - HandlerResultContent content = - handler.fetchOperationResult( - newGreetingServiceContext("sayHello2"), - OperationFetchResultDetails.newBuilder().setOperationToken(operationToken).build()); - assertEquals( - "Hello from API, SomeUser!", - new String(Objects.requireNonNull(content.getDataBytes()), StandardCharsets.UTF_8)); - // Test an async operation with a link + OperationContext octx = newGreetingServiceContext("sayHello2"); OperationStartResult resultWithLink = handler.startOperation( @@ -329,21 +298,6 @@ public OperationStartResult start( return next.start(context, details, param); } - @Override - public @Nullable Object fetchResult( - OperationContext context, OperationFetchResultDetails details) - throws OperationStillRunningException, OperationException, HandlerException { - operations.add(context.getOperation()); - return next.fetchResult(context, details); - } - - @Override - public OperationInfo fetchInfo(OperationContext context, OperationFetchInfoDetails details) - throws HandlerException { - operations.add(context.getOperation()); - return next.fetchInfo(context, details); - } - @Override public void cancel(OperationContext context, OperationCancelDetails details) throws HandlerException {