diff --git a/README.md b/README.md index 4834eb249..956f21efc 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ and building services. These examples can be used as a template for a new projec ## Usage > [!WARNING] -> Smithy-Java only supports **Java 17** or later. Older Java versions are not supported. +> Smithy-Java only supports **Java 21** or later. Older Java versions are not supported. ### Codegen (Gradle) diff --git a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/NullSigner.java b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/NullSigner.java index be89912e6..f20691f08 100644 --- a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/NullSigner.java +++ b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/NullSigner.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.auth.api; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.identity.Identity; import software.amazon.smithy.java.context.Context; @@ -31,7 +30,7 @@ private NullSigner() {} * @return the request as-is. */ @Override - public CompletableFuture sign(Object request, Identity identity, Context properties) { - return CompletableFuture.completedFuture(request); + public Object sign(Object request, Identity identity, Context properties) { + return request; } } diff --git a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/Signer.java b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/Signer.java index b8ed42902..e78f32db5 100644 --- a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/Signer.java +++ b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/Signer.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.auth.api; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.identity.Identity; import software.amazon.smithy.java.context.Context; @@ -25,7 +24,7 @@ public interface Signer extends AutoClosea * @param properties Signing properties. * @return the signed request. */ - CompletableFuture sign(RequestT request, IdentityT identity, Context properties); + RequestT sign(RequestT request, IdentityT identity, Context properties); @SuppressWarnings("unchecked") static Signer nullSigner() { diff --git a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolver.java b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolver.java index 562ba5cf8..e41832999 100644 --- a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolver.java +++ b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolver.java @@ -6,7 +6,6 @@ package software.amazon.smithy.java.auth.api.identity; import java.util.List; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.context.Context; /** @@ -20,9 +19,9 @@ public interface IdentityResolver { * error string. Unexpected errors like malformed input or networking errors are allowed to throw exceptions. * * @param requestProperties The request properties used to resolve an Identity. - * @return a CompletableFuture for the resolved identity result. + * @return the resolved identity result. */ - CompletableFuture> resolveIdentity(Context requestProperties); + IdentityResult resolveIdentity(Context requestProperties); /** * Retrieve the class of the identity resolved by this identity resolver. @@ -42,7 +41,8 @@ static IdentityResolver chain(List> } /** - * Create an implementation of {@link IdentityResolver} that returns a specific, pre-defined instance of {@link Identity}. + * Create an implementation of {@link IdentityResolver} that returns a specific, pre-defined instance of + * {@link Identity}. */ static IdentityResolver of(I identity) { return new StaticIdentityResolver<>(identity); diff --git a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolverChain.java b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolverChain.java index 268b083a5..2d737d9d0 100644 --- a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolverChain.java +++ b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/IdentityResolverChain.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.context.Context; final class IdentityResolverChain implements IdentityResolver { @@ -29,35 +28,15 @@ public Class identityType() { } @Override - public CompletableFuture> resolveIdentity(Context requestProperties) { + public IdentityResult resolveIdentity(Context requestProperties) { List> errors = new ArrayList<>(); - return executeChain(resolvers.get(0), requestProperties, errors, 0); - } - - private CompletableFuture> executeChain( - IdentityResolver resolver, - Context requestProperties, - List> errors, - int idx - ) { - var result = resolver.resolveIdentity(requestProperties); - if (idx + 1 < resolvers.size()) { - var nextResolver = resolvers.get(idx + 1); - return result.thenCompose(ir -> { - if (ir.error() != null) { - errors.add(ir); - return executeChain(nextResolver, requestProperties, errors, idx + 1); - } - return CompletableFuture.completedFuture(ir); - }); - } - - return result.thenApply(ir -> { - if (ir.error() != null) { - errors.add(ir); - return IdentityResult.ofError(IdentityResolverChain.class, "Attempted resolvers: " + errors); + for (IdentityResolver resolver : resolvers) { + IdentityResult result = resolver.resolveIdentity(requestProperties); + if (result.error() == null) { + return result; } - return ir; - }); + errors.add(result); + } + return IdentityResult.ofError(IdentityResolverChain.class, "Attempted resolvers: " + errors); } } diff --git a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/StaticIdentityResolver.java b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/StaticIdentityResolver.java index f090066df..7621434ce 100644 --- a/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/StaticIdentityResolver.java +++ b/auth-api/src/main/java/software/amazon/smithy/java/auth/api/identity/StaticIdentityResolver.java @@ -6,21 +6,20 @@ package software.amazon.smithy.java.auth.api.identity; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.context.Context; final class StaticIdentityResolver implements IdentityResolver { private final IdentityT identity; - private final CompletableFuture> result; + private final IdentityResult result; public StaticIdentityResolver(IdentityT identity) { this.identity = Objects.requireNonNull(identity); - this.result = CompletableFuture.completedFuture(IdentityResult.of(identity)); + this.result = IdentityResult.of(identity); } @Override - public CompletableFuture> resolveIdentity(Context requestProperties) { + public IdentityResult resolveIdentity(Context requestProperties) { return result; } diff --git a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/RpcEventStreamsUtil.java b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/RpcEventStreamsUtil.java index 4d3f5645b..f8a344dc8 100644 --- a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/RpcEventStreamsUtil.java +++ b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/RpcEventStreamsUtil.java @@ -32,7 +32,8 @@ public static Flow.Publisher bodyForEventStreaming( return EventStreamFrameEncodingProcessor.create(eventStream, eventStreamEncodingFactory, input); } - public static CompletableFuture deserializeResponse( + // TODO: Make more synchronous + public static O deserializeResponse( EventDecoderFactory eventDecoderFactory, DataStream bodyDataStream ) { @@ -63,7 +64,7 @@ public void onComplete() { } }); - return result; + return result.join(); } private static Schema streamingMember(Schema schema) { diff --git a/aws/aws-sigv4/src/jmh/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4SignerTrials.java b/aws/aws-sigv4/src/jmh/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4SignerTrials.java index 08d6325f3..e4a762f70 100644 --- a/aws/aws-sigv4/src/jmh/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4SignerTrials.java +++ b/aws/aws-sigv4/src/jmh/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4SignerTrials.java @@ -5,14 +5,12 @@ package software.amazon.smithy.java.aws.client.auth.scheme.sigv4; -import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.Security; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -110,9 +108,9 @@ public void setup() throws Exception { } @Benchmark - public void sign() throws IOException, ExecutionException, InterruptedException { + public void sign() { if (!skipTest) { - signer.sign(request, TEST_IDENTITY, TEST_PROPERTIES).get(); + signer.sign(request, TEST_IDENTITY, TEST_PROPERTIES); } else { System.out.println("Skipping benchmark on Windows"); } diff --git a/aws/aws-sigv4/src/main/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4Signer.java b/aws/aws-sigv4/src/main/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4Signer.java index 91495c8a4..2da67453b 100644 --- a/aws/aws-sigv4/src/main/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4Signer.java +++ b/aws/aws-sigv4/src/main/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4Signer.java @@ -22,7 +22,6 @@ import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; -import java.util.concurrent.CompletableFuture; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import software.amazon.smithy.java.auth.api.Signer; @@ -98,11 +97,7 @@ public void close() { } @Override - public CompletableFuture sign( - HttpRequest request, - AwsCredentialsIdentity identity, - Context properties - ) { + public HttpRequest sign(HttpRequest request, AwsCredentialsIdentity identity, Context properties) { var region = properties.expect(SigV4Settings.REGION); var name = properties.expect(SigV4Settings.SIGNING_NAME); var clock = properties.getOrDefault(SigV4Settings.CLOCK, Clock.systemUTC()); @@ -111,32 +106,31 @@ public CompletableFuture sign( // TODO: Support chunk encoding // TODO: support UNSIGNED - return getPayloadHash(request.body()).thenApply(payloadHash -> { - var signedHeaders = createSignedHeaders( - request.method(), - request.uri(), - request.headers(), - payloadHash, - region, - name, - clock.instant(), - identity.accessKeyId(), - identity.secretAccessKey(), - identity.sessionToken(), - !request.body().hasKnownLength()); - // Don't let the cached buffers grow too large. - var sb = signingResources.sb; - if (sb.length() > BUFFER_SIZE) { - sb.setLength(BUFFER_SIZE); - sb.trimToSize(); - } - sb.setLength(0); - return request.toBuilder().headers(HttpHeaders.of(signedHeaders)).build(); - }); + var payloadHash = getPayloadHash(request.body()); + var signedHeaders = createSignedHeaders( + request.method(), + request.uri(), + request.headers(), + payloadHash, + region, + name, + clock.instant(), + identity.accessKeyId(), + identity.secretAccessKey(), + identity.sessionToken(), + !request.body().hasKnownLength()); + // Don't let the cached buffers grow too large. + var sb = signingResources.sb; + if (sb.length() > BUFFER_SIZE) { + sb.setLength(BUFFER_SIZE); + sb.trimToSize(); + } + sb.setLength(0); + return request.toBuilder().headers(HttpHeaders.of(signedHeaders)).build(); } - private CompletableFuture getPayloadHash(DataStream dataStream) { - return dataStream.asByteBuffer().thenApply(this::hexHash); + private String getPayloadHash(DataStream dataStream) { + return hexHash(dataStream.waitForByteBuffer()); } private String hexHash(ByteBuffer bytes) { diff --git a/aws/aws-sigv4/src/test/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4TestRunner.java b/aws/aws-sigv4/src/test/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4TestRunner.java index c61635df3..688b66d49 100644 --- a/aws/aws-sigv4/src/test/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4TestRunner.java +++ b/aws/aws-sigv4/src/test/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4TestRunner.java @@ -159,7 +159,7 @@ private static HttpRequest parseRequest(String fileName) { Result createResult( Signer signer ) throws ExecutionException, InterruptedException { - var signedRequest = signer.sign(request, context.identity, context.properties).get(); + var signedRequest = signer.sign(request, context.identity, context.properties); boolean isValid = signedRequest.headers().equals(expected.headers()) && signedRequest.uri().equals(expected.uri()) && signedRequest.method().equals(expected.method()); diff --git a/aws/client/aws-client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJsonProtocol.java b/aws/client/aws-client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJsonProtocol.java index 30949d406..a99c0ac44 100644 --- a/aws/client/aws-client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJsonProtocol.java +++ b/aws/client/aws-client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJsonProtocol.java @@ -9,7 +9,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.client.http.AmznErrorHeaderExtractor; import software.amazon.smithy.java.client.http.HttpClientProtocol; import software.amazon.smithy.java.client.http.HttpErrorDeserializer; @@ -82,7 +81,7 @@ public HttpRequest } @Override - public CompletableFuture deserializeResponse( + public O deserializeResponse( ApiOperation operation, Context context, TypeRegistry typeRegistry, @@ -91,11 +90,7 @@ public CompletableF ) { // Is it an error? if (response.statusCode() != 200) { - return errorDeserializer - .createError(context, operation.schema().id(), typeRegistry, response) - .thenApply(e -> { - throw e; - }); + throw errorDeserializer.createError(context, operation.schema().id(), typeRegistry, response); } var builder = operation.outputBuilder(); @@ -103,9 +98,10 @@ public CompletableF // If the payload is empty, then use "{}" as the default empty payload. if (content.contentLength() == 0) { - return CompletableFuture.completedFuture(codec.deserializeShape(EMPTY_PAYLOAD, builder)); + return codec.deserializeShape(EMPTY_PAYLOAD, builder); } - return content.asByteBuffer().thenApply(bytes -> codec.deserializeShape(bytes, builder)); + var bytes = content.waitForByteBuffer(); + return codec.deserializeShape(bytes, builder); } } diff --git a/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolver.java b/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolver.java index bf086cc40..f4acbc987 100644 --- a/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolver.java +++ b/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolver.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.aws.client.core.identity; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.identity.IdentityResult; import software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsIdentity; import software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsResolver; @@ -33,16 +32,15 @@ public final class EnvironmentVariableIdentityResolver implements AwsCredentials + "AWS_SECRET_ACCESS_KEY environment variables"; @Override - public CompletableFuture> resolveIdentity(Context requestProperties) { + public IdentityResult resolveIdentity(Context requestProperties) { String accessKey = System.getenv(ACCESS_KEY_PROPERTY); String secretKey = System.getenv(SECRET_KEY_PROPERTY); String sessionToken = System.getenv(SESSION_TOKEN_PROPERTY); if (accessKey == null || secretKey == null) { - return CompletableFuture.completedFuture(IdentityResult.ofError(getClass(), ERROR_MESSAGE)); + return IdentityResult.ofError(getClass(), ERROR_MESSAGE); } - return CompletableFuture.completedFuture( - IdentityResult.of(AwsCredentialsIdentity.create(accessKey, secretKey, sessionToken))); + return IdentityResult.of(AwsCredentialsIdentity.create(accessKey, secretKey, sessionToken)); } } diff --git a/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolver.java b/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolver.java index 61d327d6b..bd8824f10 100644 --- a/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolver.java +++ b/aws/client/aws-client-core/src/main/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolver.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.aws.client.core.identity; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.identity.IdentityResult; import software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsIdentity; import software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsResolver; @@ -35,20 +34,15 @@ public final class SystemPropertiesIdentityResolver implements AwsCredentialsRes + "aws.secretAccessKey system properties"; @Override - public CompletableFuture> resolveIdentity(Context requestProperties) { + public IdentityResult resolveIdentity(Context requestProperties) { String accessKey = System.getProperty(ACCESS_KEY_PROPERTY); String secretKey = System.getProperty(SECRET_KEY_PROPERTY); String sessionToken = System.getProperty(SESSION_TOKEN_PROPERTY); if (accessKey != null && secretKey != null) { - return CompletableFuture.completedFuture( - IdentityResult.of( - AwsCredentialsIdentity.create( - accessKey, - secretKey, - sessionToken))); + return IdentityResult.of(AwsCredentialsIdentity.create(accessKey, secretKey, sessionToken)); } - return CompletableFuture.completedFuture(IdentityResult.ofError(getClass(), ERROR_MESSAGE)); + return IdentityResult.ofError(getClass(), ERROR_MESSAGE); } } diff --git a/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolverTest.java b/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolverTest.java index 3bd8014b9..402306cc1 100644 --- a/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolverTest.java +++ b/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/EnvironmentVariableIdentityResolverTest.java @@ -7,7 +7,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsIdentity; import software.amazon.smithy.java.context.Context; @@ -15,9 +14,9 @@ public class EnvironmentVariableIdentityResolverTest { @Test - void resolverReturnsExpectedIdentity() throws ExecutionException, InterruptedException { + void resolverReturnsExpectedIdentity() { var resolver = new EnvironmentVariableIdentityResolver(); - var value = resolver.resolveIdentity(Context.empty()).get(); + var value = resolver.resolveIdentity(Context.empty()); var expected = AwsCredentialsIdentity.create( "env_access_key", "env_secret_key", diff --git a/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolverTest.java b/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolverTest.java index b7edd6931..d1e686f64 100644 --- a/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolverTest.java +++ b/aws/client/aws-client-core/src/test/java/software/amazon/smithy/java/aws/client/core/identity/SystemPropertiesIdentityResolverTest.java @@ -7,16 +7,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsIdentity; import software.amazon.smithy.java.context.Context; public class SystemPropertiesIdentityResolverTest { @Test - void resolverReturnsExpectedIdentity() throws ExecutionException, InterruptedException { + void resolverReturnsExpectedIdentity() { var resolver = new SystemPropertiesIdentityResolver(); - var value = resolver.resolveIdentity(Context.empty()).get(); + var value = resolver.resolveIdentity(Context.empty()); var expected = AwsCredentialsIdentity.create( "property_access_key", "property_secret_key", diff --git a/aws/client/aws-client-rulesengine/src/jmh/java/software/amazon/smithy/java/aws/client/rulesengine/Bench.java b/aws/client/aws-client-rulesengine/src/jmh/java/software/amazon/smithy/java/aws/client/rulesengine/Bench.java index 260c8be17..ccff8d2e0 100644 --- a/aws/client/aws-client-rulesengine/src/jmh/java/software/amazon/smithy/java/aws/client/rulesengine/Bench.java +++ b/aws/client/aws-client-rulesengine/src/jmh/java/software/amazon/smithy/java/aws/client/rulesengine/Bench.java @@ -102,7 +102,7 @@ private static Model customizeS3Model(Model m) { } @Benchmark - public Object evaluate() throws Exception { - return endpointResolver.resolveEndpoint(endpointParams).get(); + public Object evaluate() { + return endpointResolver.resolveEndpoint(endpointParams); } } diff --git a/aws/sdkv2/aws-sdkv2-auth/src/main/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolver.java b/aws/sdkv2/aws-sdkv2-auth/src/main/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolver.java index 30ae839bc..5a515ba8a 100644 --- a/aws/sdkv2/aws-sdkv2-auth/src/main/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolver.java +++ b/aws/sdkv2/aws-sdkv2-auth/src/main/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolver.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.aws.sdkv2.auth; -import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.smithy.java.auth.api.identity.IdentityResult; @@ -38,10 +37,10 @@ public SdkCredentialsResolver(AwsCredentialsProvider sdkCredentialsProvider) { * expiration time (if available) will be included in the identity. * * @param requestProperties The request properties used to resolve an Identity. - * @return A future that completes with the resolved AWS credentials identity. + * @return the resolved AWS credentials identity. */ @Override - public CompletableFuture> resolveIdentity(Context requestProperties) { + public IdentityResult resolveIdentity(Context requestProperties) { var credentials = sdkCredentialsProvider.resolveCredentials(); AwsCredentialsIdentity identity; if (credentials instanceof AwsSessionCredentials s) { @@ -50,6 +49,6 @@ public CompletableFuture> resolveIdentity } else { identity = AwsCredentialsIdentity.create(credentials.accessKeyId(), credentials.secretAccessKey()); } - return CompletableFuture.completedFuture(IdentityResult.of(identity)); + return IdentityResult.of(identity); } } diff --git a/aws/sdkv2/aws-sdkv2-auth/src/test/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolverTest.java b/aws/sdkv2/aws-sdkv2-auth/src/test/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolverTest.java index 20d3a3940..94d9315f1 100644 --- a/aws/sdkv2/aws-sdkv2-auth/src/test/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolverTest.java +++ b/aws/sdkv2/aws-sdkv2-auth/src/test/java/software/amazon/smithy/java/aws/sdkv2/auth/SdkCredentialsResolverTest.java @@ -35,7 +35,7 @@ public void testResolveBasicCredentials() throws ExecutionException, Interrupted var resolver = new SdkCredentialsResolver(credentialsProvider); - var result = resolver.resolveIdentity(authProperties).get(); + var result = resolver.resolveIdentity(authProperties); assertNotNull(result); AwsCredentialsIdentity identity = result.identity(); @@ -58,7 +58,7 @@ public void testResolveSessionCredentialsWithExpiration() throws ExecutionExcept var resolver = new SdkCredentialsResolver(credentialsProvider); - IdentityResult result = resolver.resolveIdentity(authProperties).get(); + IdentityResult result = resolver.resolveIdentity(authProperties); assertNotNull(result); var identity = result.identity(); @@ -76,7 +76,7 @@ public void testResolveSessionCredentialsWithoutExpiration() throws ExecutionExc var resolver = new SdkCredentialsResolver(credentialsProvider); - IdentityResult result = resolver.resolveIdentity(authProperties).get(); + IdentityResult result = resolver.resolveIdentity(authProperties); assertNotNull(result); AwsCredentialsIdentity identity = result.identity(); diff --git a/aws/server/aws-server-restjson/src/main/java/software/amazon/smithy/java/aws/server/restjson/AwsRestJson1Protocol.java b/aws/server/aws-server-restjson/src/main/java/software/amazon/smithy/java/aws/server/restjson/AwsRestJson1Protocol.java index a6b128be8..786b69707 100644 --- a/aws/server/aws-server-restjson/src/main/java/software/amazon/smithy/java/aws/server/restjson/AwsRestJson1Protocol.java +++ b/aws/server/aws-server-restjson/src/main/java/software/amazon/smithy/java/aws/server/restjson/AwsRestJson1Protocol.java @@ -144,7 +144,7 @@ public CompletableFuture deserializeInput(Job job) { .payloadMediaType("application/json"); try { - deser.deserialize().get(); + deser.deserialize(); } catch (Exception e) { //TODO do exception translation. return CompletableFuture.failedFuture(e); diff --git a/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts index 0108e3186..70f71e70d 100644 --- a/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts @@ -21,7 +21,7 @@ java { tasks.withType() { options.encoding = "UTF-8" - options.release.set(17) + options.release.set(21) } tasks.withType() { diff --git a/client/client-auth-api/src/main/java/software/amazon/smithy/java/client/core/auth/scheme/NoAuthAuthScheme.java b/client/client-auth-api/src/main/java/software/amazon/smithy/java/client/core/auth/scheme/NoAuthAuthScheme.java index eb9363191..adac100b9 100644 --- a/client/client-auth-api/src/main/java/software/amazon/smithy/java/client/core/auth/scheme/NoAuthAuthScheme.java +++ b/client/client-auth-api/src/main/java/software/amazon/smithy/java/client/core/auth/scheme/NoAuthAuthScheme.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.client.core.auth.scheme; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.Signer; import software.amazon.smithy.java.auth.api.identity.Identity; import software.amazon.smithy.java.auth.api.identity.IdentityResolver; @@ -56,12 +55,10 @@ public Signer signer() { } private static final class NullIdentityResolver implements IdentityResolver { - public static final CompletableFuture> NULL_IDENTITY = CompletableFuture - .completedFuture( - IdentityResult.of(new Identity() {})); + public static final IdentityResult NULL_IDENTITY = IdentityResult.of(new Identity() {}); @Override - public CompletableFuture> resolveIdentity(Context requestProperties) { + public IdentityResult resolveIdentity(Context requestProperties) { return NULL_IDENTITY; } diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/Client.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/Client.java index 14b069e75..9de183029 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/Client.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/Client.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import software.amazon.smithy.java.auth.api.identity.IdentityResolver; import software.amazon.smithy.java.auth.api.identity.IdentityResolvers; @@ -72,7 +71,7 @@ protected Client(Builder builder) { * @param Output shape. * @return Returns the deserialized output. */ - protected CompletableFuture call( + protected O call( I input, ApiOperation operation, RequestOverrideConfig overrideConfig @@ -94,7 +93,6 @@ protected Completab } else { callConfig = afterInterceptionConfig; } - } // Rebuild the pipeline, resolvers, etc if the config changed. diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientConfig.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientConfig.java index e34cd6b33..3cfef6026 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientConfig.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientConfig.java @@ -56,7 +56,7 @@ public final class ClientConfig { private ClientConfig(Builder builder) { // Transports can change between builders to toBuilder. Transports can modify the builder when they're applied. - // To prevent a previous configuration meant for one transport to impact a future configuration, we create a + // To prevent a previous configuration meant for one transport to impact other configurations, we create a // copy of the original builder. We also don't want to apply the transport modifications multiple times. // This builder is used in toBuilder. this.originalBuilder = builder.copyBuilder(); diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientPipeline.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientPipeline.java index 9ea6b1ade..ebf811f60 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientPipeline.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientPipeline.java @@ -11,13 +11,6 @@ import java.util.HashSet; import java.util.Objects; import java.util.StringJoiner; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; import software.amazon.smithy.java.auth.api.identity.Identity; import software.amazon.smithy.java.auth.api.identity.IdentityResolvers; import software.amazon.smithy.java.auth.api.identity.IdentityResult; @@ -52,7 +45,6 @@ */ final class ClientPipeline { - private static final ScheduledExecutorService SCHEDULER = Executors.newSingleThreadScheduledExecutor(); private static final InternalLogger LOGGER = InternalLogger.getLogger(ClientPipeline.class); private static final URI UNRESOLVED; @@ -108,7 +100,7 @@ static void validateProtocolAndTransport(ClientProtocol protocol, ClientTr } } - CompletableFuture send(ClientCall call) { + O send(ClientCall call) { var input = call.input; // Always start the attempt count at 1. @@ -143,7 +135,7 @@ CompletableFuture CompletableFuture acquireRetryToken( + private O acquireRetryToken( ClientCall call, RequestHook requestHook ) { @@ -153,18 +145,17 @@ private Completable var result = call.retryStrategy.acquireInitialToken(new AcquireInitialTokenRequest(call.retryScope)); call.retryToken = result.token(); // Delay if the initial request is pre-emptively throttled. - if (result.delay().toMillis() == 0) { - return doSendOrRetry(call, requestHook); - } else { - return sendAfterDelay(result.delay(), call, requestHook, this::doSendOrRetry); + if (result.delay().toMillis() > 0) { + sleep(result.delay()); } + return doSendOrRetry(call, requestHook); } catch (TokenAcquisitionFailedException e) { // Don't send the request if the circuit breaker prevents it. - return CompletableFuture.failedFuture(e); + throw e; } } - private CompletableFuture doSendOrRetry( + private O doSendOrRetry( ClientCall call, RequestHook requestHook ) { @@ -181,11 +172,11 @@ private Completable call.interceptor.readBeforeSigning(finalHook); var resolvedAuthScheme = resolveAuthScheme(call, request); - return resolvedAuthScheme.identity() - .thenCompose(identityResult -> afterIdentity(call, finalHook, identityResult, resolvedAuthScheme)); + var identityResult = resolvedAuthScheme.identity(); + return afterIdentity(call, finalHook, identityResult, resolvedAuthScheme); } - private CompletableFuture afterIdentity( + private O afterIdentity( ClientCall call, RequestHook requestHook, IdentityResult identityResult, @@ -196,30 +187,27 @@ private Completable call.context.put(CallContext.IDENTITY, identity); // TODO: what to do with supportedAuthSchemes of an endpoint? - return resolveEndpoint(call) - .thenApply(endpoint -> { - call.context.put(CallContext.ENDPOINT, endpoint); - return protocol.setServiceEndpoint(requestHook.request(), endpoint); - }) - .thenCompose(resolvedAuthScheme::sign) - .thenApply(req -> { - var updatedHook = requestHook.withRequest(req); - call.interceptor.readAfterSigning(updatedHook); - req = call.interceptor.modifyBeforeTransmit(updatedHook); - // Track the used idempotency token, if any. - setIdemTokenValue(call.operation, call.context, call.input); - call.interceptor.readBeforeTransmit(updatedHook.withRequest(req)); - return req; - }) - .thenCompose(finalRequest -> { - return transport - .send(call.context, finalRequest) - .exceptionally(e -> { - // In case the transport doesn't do the remapping, do that here now. - throw ClientTransport.remapExceptions(e); - }) - .thenCompose(response -> deserialize(call, finalRequest, response, call.interceptor)); - }); + Endpoint endpoint = resolveEndpoint(call); + call.context.put(CallContext.ENDPOINT, endpoint); + + RequestT req = protocol.setServiceEndpoint(requestHook.request(), endpoint); + req = resolvedAuthScheme.sign(req); + + var updatedHook = requestHook.withRequest(req); + call.interceptor.readAfterSigning(updatedHook); + req = call.interceptor.modifyBeforeTransmit(updatedHook); + + // Track the used idempotency token, if any. + setIdemTokenValue(call.operation, call.context, call.input); + call.interceptor.readBeforeTransmit(updatedHook.withRequest(req)); + + try { + ResponseT response = transport.send(call.context, req); + return deserialize(call, req, response, call.interceptor); + } catch (Exception e) { + // In case the transport doesn't do the remapping, do that here now. + throw ClientTransport.remapExceptions(e); + } } private static void setIdemTokenValue(ApiOperation operation, Context context, SerializableStruct input) { @@ -289,20 +277,17 @@ private ResolvedScheme createR private record ResolvedScheme( Context signerProperties, AuthScheme authScheme, - CompletableFuture> identity) { - public CompletableFuture sign(RequestT request) { - return identity.thenCompose(identity -> { - // Throws when no identity is found. - var resolvedIdentity = identity.unwrap(); - var signer = authScheme.signer(); - var result = signer.sign(request, resolvedIdentity, signerProperties); - result.whenComplete((res, err) -> signer.close()); - return result; - }); + IdentityResult identity) { + public RequestT sign(RequestT request) { + // Throws when no identity is found. + var resolvedIdentity = identity.unwrap(); + try (var signer = authScheme.signer()) { + return signer.sign(request, resolvedIdentity, signerProperties); + } } } - private CompletableFuture resolveEndpoint( + private Endpoint resolveEndpoint( ClientCall call ) { var request = EndpointResolverParams.builder() @@ -313,8 +298,7 @@ private Completable return call.endpointResolver.resolveEndpoint(request); } - @SuppressWarnings("unchecked") - private CompletableFuture deserialize( + private O deserialize( ClientCall call, RequestT request, ResponseT response, @@ -333,93 +317,87 @@ private Completable interceptor.readBeforeDeserialization(responseHook); - return protocol.deserializeResponse(call.operation, context, call.typeRegistry, request, modifiedResponse) - .handle((shape, thrown) -> { - RuntimeException error = null; - if (thrown instanceof CompletionException ce) { - thrown = ce.getCause() != null ? ce.getCause() : ce; - } - if (thrown != null) { - if (!(thrown instanceof RuntimeException)) { - return CompletableFuture.failedFuture(thrown); - } - error = (RuntimeException) thrown; - } - - var outputHook = new OutputHook<>(call.operation, context, input, request, response, shape); - - try { - interceptor.readAfterDeserialization(outputHook, error); - } catch (RuntimeException e) { - error = swapError("readAfterDeserialization", error, e); - } - - try { - shape = interceptor.modifyBeforeAttemptCompletion(outputHook, error); - outputHook = outputHook.withOutput(shape); - // The expectation is that errors are re-thrown by hooks, or else they are disassociated. - error = null; - } catch (RuntimeException e) { - error = swapError("modifyBeforeAttemptCompletion", error, e); - } - - try { - interceptor.readAfterAttempt(outputHook, error); - } catch (RuntimeException e) { - error = swapError("readAfterAttempt", error, e); - } - - // 9.a If error is a retryable failure: - if (error != null && !call.isRetryDisallowed()) { - try { - // If it's retryable, keep retrying and jump to step 8a. - var acquireRequest = new RefreshRetryTokenRequest(call.retryToken, error, null); - var acquireResult = call.retryStrategy.refreshRetryToken(acquireRequest); - return retry(call, request, acquireResult.token(), acquireResult.delay()); - } catch (TokenAcquisitionFailedException tafe) { - // 9.b If InterceptorContext.response() is an unretryable failure, continue to step 10. - LOGGER.debug("Cannot acquire a retry token: {}", tafe); - } - } - - // Clear out the retry token. - var token = call.retryToken; - call.retryToken = null; - - // 9.c.i If successful: RetryStrategy: Invoke RecordSuccess. - if (error == null) { - try { - call.retryStrategy.recordSuccess(new RecordSuccessRequest(token)); - } catch (RuntimeException e) { - error = e; - } - } - - // 10. Interceptors: Invoke ModifyBeforeCompletion. (End of retry loop). - try { - shape = interceptor.modifyBeforeCompletion(outputHook, error); - outputHook = outputHook.withOutput(shape); - error = null; - } catch (RuntimeException e) { - error = swapError("modifyBeforeCompletion", error, e); - } - - // TODO: 11. TraceProbe: Invoke DispatchEvents. - - // 12. Interceptors: Invoke ReadAfterExecution. - try { - interceptor.readAfterExecution(outputHook, error); - } catch (RuntimeException e) { - error = swapError("readAfterExecution", error, e); - } - - if (error != null) { - return CompletableFuture.failedFuture(error); - } else { - return CompletableFuture.completedFuture(outputHook.output()); - } - }) - .thenCompose(o -> (CompletionStage) o); + O shape = null; + RuntimeException error = null; + + try { + shape = protocol.deserializeResponse(call.operation, context, call.typeRegistry, request, modifiedResponse); + } catch (RuntimeException e) { + error = e; + } + + var outputHook = new OutputHook<>(call.operation, context, input, request, response, shape); + + try { + interceptor.readAfterDeserialization(outputHook, error); + } catch (RuntimeException e) { + error = swapError("readAfterDeserialization", error, e); + } + + try { + shape = interceptor.modifyBeforeAttemptCompletion(outputHook, error); + outputHook = outputHook.withOutput(shape); + // The expectation is that errors are re-thrown by hooks, or else they are disassociated. + error = null; + } catch (RuntimeException e) { + error = swapError("modifyBeforeAttemptCompletion", error, e); + } + + try { + interceptor.readAfterAttempt(outputHook, error); + } catch (RuntimeException e) { + error = swapError("readAfterAttempt", error, e); + } + + // 9.a If error is a retryable failure: + if (error != null && !call.isRetryDisallowed()) { + try { + // If it's retryable, keep retrying and jump to step 8a. + var acquireRequest = new RefreshRetryTokenRequest(call.retryToken, error, null); + var acquireResult = call.retryStrategy.refreshRetryToken(acquireRequest); + return retry(call, request, acquireResult.token(), acquireResult.delay()); + } catch (TokenAcquisitionFailedException tafe) { + // 9.b If InterceptorContext.response() is an unretryable failure, continue to step 10. + LOGGER.debug("Cannot acquire a retry token: {}", tafe); + } + } + + // Clear out the retry token. + var token = call.retryToken; + call.retryToken = null; + + // 9.c.i If successful: RetryStrategy: Invoke RecordSuccess. + if (error == null) { + try { + call.retryStrategy.recordSuccess(new RecordSuccessRequest(token)); + } catch (RuntimeException e) { + error = e; + } + } + + // 10. Interceptors: Invoke ModifyBeforeCompletion. (End of retry loop). + try { + shape = interceptor.modifyBeforeCompletion(outputHook, error); + outputHook = outputHook.withOutput(shape); + error = null; + } catch (RuntimeException e) { + error = swapError("modifyBeforeCompletion", error, e); + } + + // TODO: 11. TraceProbe: Invoke DispatchEvents. + + // 12. Interceptors: Invoke ReadAfterExecution. + try { + interceptor.readAfterExecution(outputHook, error); + } catch (RuntimeException e) { + error = swapError("readAfterExecution", error, e); + } + + if (error != null) { + throw error; + } + + return outputHook.output(); } private static RuntimeException swapError(String hook, RuntimeException oldE, RuntimeException newE) { @@ -429,7 +407,7 @@ private static RuntimeException swapError(String hook, RuntimeException oldE, Ru return newE; } - private CompletableFuture retry( + private O retry( ClientCall call, RequestT request, RetryToken retryToken, @@ -442,34 +420,19 @@ private Completable var requestHook = new RequestHook<>(call.operation, call.context, call.input, request); - if (after.toMillis() == 0) { - // Immediate retry. - return doSendOrRetry(call, requestHook); - } else { - // Retry after a delay. - return sendAfterDelay(after, call, requestHook, this::doSendOrRetry); + if (after.toMillis() > 0) { + sleep(after); } + + return doSendOrRetry(call, requestHook); } - private static CompletableFuture sendAfterDelay( - Duration after, - ClientCall call, - V value, - BiFunction, V, CompletableFuture> result - ) { - var millis = after.toMillis(); - if (millis <= 0) { - throw new IllegalArgumentException("Send after delay duration is <= 0: " + after); + private static void sleep(Duration duration) { + try { + Thread.sleep(duration.toMillis()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for retry delay", e); } - CompletableFuture future = new CompletableFuture<>(); - SCHEDULER.schedule(() -> { - try { - result.apply(call, value).thenApply(future::complete); - } catch (Exception e) { - future.completeExceptionally(e); - } - }, millis, TimeUnit.MILLISECONDS); - return future; } } diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientProtocol.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientProtocol.java index a203fccfe..5b4627ade 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientProtocol.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientProtocol.java @@ -6,7 +6,6 @@ package software.amazon.smithy.java.client.core; import java.net.URI; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.client.core.endpoint.Endpoint; import software.amazon.smithy.java.context.Context; import software.amazon.smithy.java.core.error.CallException; @@ -89,7 +88,7 @@ RequestT createRequ * @return the deserialized output shape. * @throws CallException if an error occurs, including deserialized modeled errors and protocol errors. */ - CompletableFuture deserializeResponse( + O deserializeResponse( ApiOperation operation, Context context, TypeRegistry errorRegistry, diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java index fdcd30b8b..26f0e469e 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java @@ -9,7 +9,6 @@ import java.net.ProtocolException; import java.net.SocketException; import java.net.SocketTimeoutException; -import java.util.concurrent.CompletableFuture; import javax.net.ssl.SSLException; import software.amazon.smithy.java.client.core.error.ConnectTimeoutException; import software.amazon.smithy.java.client.core.error.TlsException; @@ -43,9 +42,9 @@ public interface ClientTransport extends ClientPlugin { * * @param context Call context. * @param request Request to send. - * @return a CompletableFuture that is completed with the response. + * @return the response. */ - CompletableFuture send(Context context, RequestT request); + ResponseT send(Context context, RequestT request); /** * Get the message exchange. @@ -69,21 +68,15 @@ default void configureClient(ClientConfig.Builder config) { * @return the remapped exception. A given {@code CallException} or {@code TransportException} is returned as-is. */ static CallException remapExceptions(Throwable e) { - if (e instanceof CallException ce) { - return ce; // rethrow CallException and TransportException as-is. - } else if (e instanceof ConnectException) { - return new ConnectTimeoutException(e); - } else if (e instanceof SocketTimeoutException) { - return new TransportSocketTimeout(e); - } else if (e instanceof SocketException) { - return new TransportSocketException(e); - } else if (e instanceof SSLException) { - return new TlsException(e); - } else if (e instanceof ProtocolException) { - return new TransportProtocolException(e); - } else { + return switch (e) { + case CallException ce -> ce; // rethrow CallException and TransportException as-is. + case ConnectException connectException -> new ConnectTimeoutException(e); + case SocketTimeoutException socketTimeoutException -> new TransportSocketTimeout(e); + case SocketException socketException -> new TransportSocketException(e); + case SSLException sslException -> new TlsException(e); + case ProtocolException protocolException -> new TransportProtocolException(e); // Wrap all other exceptions as a TransportException. - return new TransportException(e); - } + case null, default -> new TransportException(e); + }; } } diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolver.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolver.java index 24842640e..475570f6e 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolver.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolver.java @@ -7,7 +7,6 @@ import java.net.URI; import java.util.Objects; -import java.util.concurrent.CompletableFuture; /** * Resolves an endpoint for an operation. @@ -18,9 +17,9 @@ public interface EndpointResolver { * Resolves an endpoint using the provided parameters. * * @param params The parameters used during endpoint resolution. - * @return a CompletableFuture for the resolved endpoint. + * @return a resolved endpoint. */ - CompletableFuture resolveEndpoint(EndpointResolverParams params); + Endpoint resolveEndpoint(EndpointResolverParams params); /** * Create a default endpoint resolver that always returns the same endpoint with prefixes added if relevant. diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/HostLabelEndpointResolver.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/HostLabelEndpointResolver.java index ec6479b26..5edd04a25 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/HostLabelEndpointResolver.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/HostLabelEndpointResolver.java @@ -8,7 +8,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Locale; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.core.schema.TraitKey; /** @@ -18,14 +17,14 @@ */ record HostLabelEndpointResolver(EndpointResolver delegate) implements EndpointResolver { @Override - public CompletableFuture resolveEndpoint(EndpointResolverParams params) { + public Endpoint resolveEndpoint(EndpointResolverParams params) { var endpointTrait = params.operation().schema().getTrait(TraitKey.ENDPOINT_TRAIT); if (endpointTrait == null) { return delegate.resolveEndpoint(params); } var prefix = HostLabelSerializer.resolvePrefix(endpointTrait.getHostPrefix(), params.inputValue()); - - return delegate.resolveEndpoint(params).thenApply(endpoint -> prefix(endpoint, prefix)); + var endpoint = delegate.resolveEndpoint(params); + return prefix(endpoint, prefix); } private static Endpoint prefix(Endpoint endpoint, String prefix) { diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/StaticHostResolver.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/StaticHostResolver.java index 04ea56435..25438f57d 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/StaticHostResolver.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/StaticHostResolver.java @@ -5,8 +5,6 @@ package software.amazon.smithy.java.client.core.endpoint; -import java.util.concurrent.CompletableFuture; - /** * An endpoint resolver that always returns the same endpoint. * @@ -14,7 +12,7 @@ */ record StaticHostResolver(Endpoint endpoint) implements EndpointResolver { @Override - public CompletableFuture resolveEndpoint(EndpointResolverParams params) { - return CompletableFuture.completedFuture(endpoint); + public Endpoint resolveEndpoint(EndpointResolverParams params) { + return endpoint; } } diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/AsyncPaginator.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/AsyncPaginator.java deleted file mode 100644 index c862f9ddd..000000000 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/AsyncPaginator.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.client.core.pagination; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Flow; -import java.util.function.Predicate; -import software.amazon.smithy.java.client.core.RequestOverrideConfig; -import software.amazon.smithy.java.core.schema.ApiOperation; -import software.amazon.smithy.java.core.schema.SerializableStruct; - -/** - * Asynchronous paginator that automates the retrieval of paginated results from a service. - * - *

Many list operations return paginated results when the response object is too large - * to return in a single response. Paginators can help you navigate through paginated responses - * from services. Typically, a service will return a truncated response when the response length - * is greater than a certain limit. This response will also contain a next token to use in a - * subsequent request. In order to consume all the data, you could create a loop that makes - * multiple requests, replacing the next token in each iteration. However, with paginators this - * extra loop isn’t necessary because the Paginator handles all of that logic for you; - * in other words, you can retrieve all the data with less code. - * - *

To consume the paginated data from this paginator, implement a {@link Flow.Subscriber} and subscribe it - * to this flow. Each time your subscriber requests a new result, this paginator will request a new page of results from - * the service. The following example shows how to process each page of results from a paginator: - * - *

{@code
- *  // Page through all results till we find some specific item.
- *  paginator.forEach(result -> {
- *      if (result.items().stream().anyMatch(item -> "targetId".equals(item.id())) {
- *          // Do something with the result.
- *          logSpecialItem(result);
- *          // Stop paginating as we have found the item we are looking for
- *          return false;
- *      } else {
- *          // Otherwise keep paging through results
- *          return true;
- *      }
- *  })
- * }
- * - *

Note:This paginator expects fully-resolved paginated traits on any paginated operation schemas - * and will not automatically merge operation pagination info with a service's pagination info. - * - * @param Output type of list operation being paginated. - */ -public interface AsyncPaginator extends PaginatorSettings, Flow.Publisher { - - /** - * Interface representing a function that is asynchronously paginatable. - */ - @FunctionalInterface - interface PaginatableAsync { - CompletableFuture call(I input, RequestOverrideConfig requestContext); - } - - /** - * Create a new {@link AsyncPaginator} for a given operation and input. - * - * @param input Base input to use for repeated requests to service. - * @param operation API model for operation being paginated. - * @param call Asynchronous call that retrieves pages from service. - * @return Asynchronous paginator - * @param Operation input shape type. - * @param Operation output shape type. - */ - static AsyncPaginator paginate( - I input, - ApiOperation operation, - PaginatableAsync call - ) { - return new DefaultAsyncPaginator<>(input, operation, call); - } - - /** - * Subscribes to the publisher with the given Consumer. - * - *

This consumer will be called for each event published (without backpressure). If more control or - * backpressure is required, consider using {@link Flow.Publisher#subscribe(Flow.Subscriber)}. - * - * @param predicate Consumer to process pages of results. Return true from predicate to keep processing next page, false to stop. - * @return CompletableFuture that will be notified when all events have been consumed or if an error occurs. - */ - default CompletableFuture forEach(Predicate predicate) { - var future = new CompletableFuture(); - subscribe(new Flow.Subscriber<>() { - private Flow.Subscription subscription; - - @Override - public void onSubscribe(Flow.Subscription subscription) { - this.subscription = subscription; - subscription.request(1); - } - - @Override - public void onNext(O item) { - try { - if (predicate.test(item)) { - subscription.request(1); - } else { - subscription.cancel(); - future.complete(null); - } - } catch (RuntimeException exc) { - // Handle the consumer throwing an exception - subscription.cancel(); - future.completeExceptionally(exc); - } - } - - @Override - public void onError(Throwable throwable) { - future.completeExceptionally(throwable); - } - - @Override - public void onComplete() { - future.complete(null); - } - }); - return future; - } -} diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/DefaultAsyncPaginator.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/DefaultAsyncPaginator.java deleted file mode 100644 index 328cf82ed..000000000 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/DefaultAsyncPaginator.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.client.core.pagination; - -import java.util.Objects; -import java.util.concurrent.Flow; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import software.amazon.smithy.java.client.core.RequestOverrideConfig; -import software.amazon.smithy.java.core.schema.ApiOperation; -import software.amazon.smithy.java.core.schema.SerializableStruct; -import software.amazon.smithy.java.core.schema.TraitKey; - -final class DefaultAsyncPaginator implements - AsyncPaginator { - - private final PaginatableAsync call; - private final PaginationInputSetter inputFactory; - private final PaginationTokenExtractor extractor; - - // Pagination parameters - private volatile String nextToken = null; - private int pageSize; - private int totalMaxItems = 0; - - // Request override for paginated requests - private RequestOverrideConfig overrideConfig = null; - - DefaultAsyncPaginator(I input, ApiOperation operation, PaginatableAsync call) { - this.call = call; - var trait = operation.schema().expectTrait(TraitKey.PAGINATED_TRAIT); - // Input and output token paths are expected to be set. - var inputTokenMember = trait.getInputToken().orElseThrow(); - var outputTokenPath = trait.getOutputToken().orElseThrow(); - - // Page size and Items are optional - var pageSizeMember = trait.getPageSize().orElse(null); - var itemsPath = trait.getItems().orElse(null); - - this.inputFactory = new PaginationInputSetter<>( - input, - operation, - inputTokenMember, - pageSizeMember); - - if (pageSizeMember != null) { - var pageSizeSchema = input.schema().member(pageSizeMember); - pageSize = input.getMemberValue(pageSizeSchema); - } - - this.extractor = new PaginationTokenExtractor( - operation.outputSchema(), - outputTokenPath, - itemsPath); - } - - @Override - public void maxItems(int maxItems) { - this.totalMaxItems = maxItems; - } - - @Override - public void overrideConfig(RequestOverrideConfig overrideConfig) { - this.overrideConfig = overrideConfig; - } - - @Override - public void subscribe(Flow.Subscriber subscriber) { - subscriber.onSubscribe(new Flow.Subscription() { - - private final AtomicInteger remainingItems = new AtomicInteger(totalMaxItems); - private final AtomicLong pendingRequests = new AtomicLong(0); - private final AtomicInteger pendingExecutions = new AtomicInteger(); - private final AtomicBoolean completed = new AtomicBoolean(false); - private int maxItems = pageSize; - - @Override - public void request(long n) { - if (n <= 0) { - subscriber.onError(new IllegalArgumentException("Requested items must be greater than 0")); - } - accumulate(pendingRequests, n); - execute(); - } - - @Override - public void cancel() { - // Do nothing - } - - private void execute() { - // Only allow one pending execution at a time. - if (pendingExecutions.getAndIncrement() > 0) { - // Cancel this execution - pendingExecutions.decrementAndGet(); - return; - } - - if (completed.get()) { - return; - } - - try { - // If there are fewer items remaining than we would request, reduce page size to match remaining. - var remaining = remainingItems.get(); - if (remaining > 0 && maxItems > remaining) { - maxItems = remaining; - } - - // Get the updated input call with new values. - var input = inputFactory.create(nextToken, maxItems); - - // The call and callback processing the output could be executed on a separate thread. - call.call(input, overrideConfig).thenAccept(output -> { - var res = extractor.extract(output); - - // If we see the same pagination token twice then stop pagination. - if (nextToken != null && Objects.equals(nextToken, res.token())) { - completed.set(true); - subscriber.onComplete(); - } - // Update token value for next call - nextToken = res.token(); - - // Update remaining items to get based on output values - var newRemaining = remainingItems.addAndGet(-res.totalItems()); - - // Send output to subscriber - subscriber.onNext(output); - - // Next token is null or max results reached, indicating there are no more values. - if (nextToken == null || (totalMaxItems != 0 && newRemaining == 0)) { - completed.set(true); - subscriber.onComplete(); - - // There is no need to update pending requests or pending executions. Just exit. - return; - } - - // Check if there are remaining requests or executions - var remainingRequests = pendingRequests.decrementAndGet(); - // Mark the current execution as complete - pendingExecutions.decrementAndGet(); - - // If there are still remaining requests start another execution - if (remainingRequests > 0) { - execute(); - } - }); - } catch (Exception exc) { - subscriber.onError(exc); - } - } - }); - } - - private static void accumulate(AtomicLong l, long n) { - l.accumulateAndGet(n, DefaultAsyncPaginator::accumulate); - } - - private static long accumulate(long current, long n) { - if (current == Long.MAX_VALUE || n == Long.MAX_VALUE) { - return Long.MAX_VALUE; - } - - try { - return Math.addExact(current, n); - } catch (ArithmeticException e) { - return Long.MAX_VALUE; - } - } -} diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/PaginationTokenExtractor.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/PaginationTokenExtractor.java index 17fc06d40..a05c29a8e 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/PaginationTokenExtractor.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/PaginationTokenExtractor.java @@ -46,12 +46,12 @@ Result extract(O outputShape) { var items = getValueForPath(itemsPathSchemas, outputShape); int totalItems = 0; if (items != null) { - if (items instanceof Collection ic) { - totalItems = ic.size(); - } else if (items instanceof Map im) { - totalItems = im.size(); - } else if (items instanceof Document doc) { - totalItems = doc.size(); + switch (items) { + case Collection ic -> totalItems = ic.size(); + case Map im -> totalItems = im.size(); + case Document doc -> totalItems = doc.size(); + default -> { + } } } return new Result(token, totalItems); diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/Paginator.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/Paginator.java index c90986055..83de671e8 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/Paginator.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/Paginator.java @@ -52,7 +52,7 @@ interface Paginatable Operation input shape type. * @param Operation output shape type. diff --git a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/ClientTest.java b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/ClientTest.java index a3a1c14cb..95113e535 100644 --- a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/ClientTest.java +++ b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/ClientTest.java @@ -120,7 +120,7 @@ public void correctlyWrapsTransportExceptions() throws URISyntaxException { .build(); var exception = Assertions.assertThrows(TransportException.class, () -> c.call("GetSprocket")); - assertSame(exception.getCause(), expectedException); + assertSame(expectedException, exception.getCause()); } @Test diff --git a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/auth/identity/IdentityResolverTest.java b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/auth/identity/IdentityResolverTest.java index ea86ff735..ce638b387 100644 --- a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/auth/identity/IdentityResolverTest.java +++ b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/auth/identity/IdentityResolverTest.java @@ -10,8 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.auth.api.identity.IdentityNotFoundException; import software.amazon.smithy.java.auth.api.identity.IdentityResolver; @@ -26,7 +25,7 @@ public class IdentityResolverTest { @Test void testStaticIdentityReturnsExpected() { assertEquals(TEST_RESOLVER.identityType(), TEST_IDENTITY.getClass()); - var resolved = TEST_RESOLVER.resolveIdentity(Context.empty()).join(); + var resolved = TEST_RESOLVER.resolveIdentity(Context.empty()); assertEquals(IdentityResult.of(TEST_IDENTITY), resolved); } @@ -38,7 +37,7 @@ void testIdentityResolverChainContinuesOnIdentityNotFound() { EmptyResolver.INSTANCE, EmptyResolver.INSTANCE, TEST_RESOLVER)); - var result = resolver.resolveIdentity(Context.empty()).join(); + var result = resolver.resolveIdentity(Context.empty()); assertEquals(result, IdentityResult.of(TEST_IDENTITY)); } @@ -50,8 +49,8 @@ void testIdentityResolverFailsOutOnUnknownError() { EmptyResolver.INSTANCE, FailingResolver.INSTANCE)); var exc = assertThrows( - CompletionException.class, - () -> resolver.resolveIdentity(Context.empty()).join()); + IllegalArgumentException.class, + () -> resolver.resolveIdentity(Context.empty())); assertTrue(exc.getMessage().contains("BAD!")); } @@ -62,16 +61,16 @@ void testIdentityResolverAggregatesExceptions() { EmptyResolver.INSTANCE, EmptyResolver.INSTANCE, EmptyResolver.INSTANCE)); - var result = resolver.resolveIdentity(Context.empty()).join(); - assertTrue( - result.error() - .contains( - "[IdentityResult[error='No token. Womp Womp.', resolver=software.amazon.smithy.java.client.core.auth.identity.IdentityResolverTest$EmptyResolver], " - + "IdentityResult[error='No token. Womp Womp.', resolver=software.amazon.smithy.java.client.core.auth.identity.IdentityResolverTest$EmptyResolver], " - + "IdentityResult[error='No token. Womp Womp.', resolver=software.amazon.smithy.java.client.core.auth.identity.IdentityResolverTest$EmptyResolver]]")); + var e = Assertions.assertThrows( + IdentityNotFoundException.class, + () -> resolver.resolveIdentity(Context.empty()).unwrap()); - var e = assertThrows(IdentityNotFoundException.class, result::unwrap); + assertTrue(e.getMessage() + .contains( + "[IdentityResult[error='No token. Womp Womp.', resolver=software.amazon.smithy.java.client.core.auth.identity.IdentityResolverTest$EmptyResolver], " + + "IdentityResult[error='No token. Womp Womp.', resolver=software.amazon.smithy.java.client.core.auth.identity.IdentityResolverTest$EmptyResolver], " + + "IdentityResult[error='No token. Womp Womp.', resolver=software.amazon.smithy.java.client.core.auth.identity.IdentityResolverTest$EmptyResolver]]")); assertTrue(e.getMessage().startsWith("Unable to resolve an identity: Attempted resolvers: ")); } @@ -83,8 +82,8 @@ private static final class EmptyResolver implements IdentityResolver> resolveIdentity(Context requestProperties) { - return CompletableFuture.completedFuture(IdentityResult.ofError(getClass(), "No token. Womp Womp.")); + public IdentityResult resolveIdentity(Context requestProperties) { + return IdentityResult.ofError(getClass(), "No token. Womp Womp."); } @Override @@ -94,15 +93,15 @@ public Class identityType() { } /** - * Always returns a failed future with a {@link IllegalArgumentException}. + * Always throws {@link IllegalArgumentException}. */ private static final class FailingResolver implements IdentityResolver { private static final FailingResolver INSTANCE = new FailingResolver(); private static final IllegalArgumentException ILLEGAL_ARGUMENT_EXCEPTION = new IllegalArgumentException("BAD!"); @Override - public CompletableFuture> resolveIdentity(Context requestProperties) { - return CompletableFuture.failedFuture(ILLEGAL_ARGUMENT_EXCEPTION); + public IdentityResult resolveIdentity(Context requestProperties) { + throw ILLEGAL_ARGUMENT_EXCEPTION; } @Override diff --git a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolverTest.java b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolverTest.java index ca41e4bb4..a976b1255 100644 --- a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolverTest.java +++ b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolverTest.java @@ -34,8 +34,7 @@ public void returnsStaticHostIgnoringHostLabel() { EndpointResolverParams.builder() .operation(new TestOperationTemplatePrefix()) .inputValue(new EndpointInput("name", "bar", "baz")) - .build()) - .join(); + .build()); MatcherAssert.assertThat( endpoint.uri().toString(), @@ -49,8 +48,7 @@ public void returnsStaticEndpointWithStaticPrefix() { EndpointResolverParams.builder() .operation(new TestOperationStaticPrefix()) .inputValue(new EndpointInput("name", "bar", "baz")) - .build()) - .join(); + .build()); MatcherAssert.assertThat( endpoint.uri().toString(), @@ -64,8 +62,7 @@ public void returnsStaticEndpointWithTemplatedPrefix() { EndpointResolverParams.builder() .operation(new TestOperationTemplatePrefix()) .inputValue(new EndpointInput("name", "bar", "baz")) - .build()) - .join(); + .build()); MatcherAssert.assertThat( endpoint.uri().toString(), diff --git a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/pagination/AsyncPaginationTest.java b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/pagination/AsyncPaginationTest.java deleted file mode 100644 index 8cc917082..000000000 --- a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/pagination/AsyncPaginationTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.client.core.pagination; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Flow; -import java.util.concurrent.LinkedBlockingQueue; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.client.core.pagination.models.GetFoosInput; -import software.amazon.smithy.java.client.core.pagination.models.GetFoosOutput; -import software.amazon.smithy.java.client.core.pagination.models.ResultWrapper; -import software.amazon.smithy.java.client.core.pagination.models.TestOperationPaginated; - -public class AsyncPaginationTest { - private static final List BASE_EXPECTED_RESULTS = List.of( - new GetFoosOutput(new ResultWrapper("first", List.of("foo0", "foo1"))), - new GetFoosOutput(new ResultWrapper("second", List.of("foo0", "foo1"))), - new GetFoosOutput(new ResultWrapper("third", List.of("foo0", "foo1"))), - new GetFoosOutput(new ResultWrapper("final", List.of("foo0", "foo1"))), - new GetFoosOutput(new ResultWrapper(null, List.of("foo0", "foo1")))); - - private MockClient mockClient; - - @BeforeEach - public void setup() { - mockClient = new MockClient(); - } - - @Test - void testAsyncPagination() { - var input = GetFoosInput.builder().maxResults(2).build(); - var paginator = AsyncPaginator.paginate(input, new TestOperationPaginated(), mockClient::getFoosAsync); - var subscriber = new PaginationTestSubscriber(); - paginator.subscribe(subscriber); - // Block and wait on results - var results = subscriber.results(); - assertThat(results, contains(BASE_EXPECTED_RESULTS.toArray())); - } - - @Test - void testMaxItemsPagination() { - var input = GetFoosInput.builder().maxResults(4).build(); - var paginator = AsyncPaginator.paginate(input, new TestOperationPaginated(), mockClient::getFoosAsync); - paginator.maxItems(10); - var subscriber = new PaginationTestSubscriber(); - paginator.subscribe(subscriber); - - // Block and wait on results - var results = subscriber.results(); - var expectedResult = List.of( - new GetFoosOutput(new ResultWrapper("first", List.of("foo0", "foo1", "foo2", "foo3"))), - new GetFoosOutput(new ResultWrapper("second", List.of("foo0", "foo1", "foo2", "foo3"))), - new GetFoosOutput(new ResultWrapper("third", List.of("foo0", "foo1")))); - assertThat(results, contains(expectedResult.toArray())); - } - - @Test - void testForEachPagination() { - var input = GetFoosInput.builder().maxResults(4).build(); - var paginator = AsyncPaginator.paginate(input, new TestOperationPaginated(), mockClient::getFoosAsync); - paginator.maxItems(10); - List results = new ArrayList<>(); - // Block and wait on results - paginator.forEach(results::add).join(); - - var expectedResult = List.of( - new GetFoosOutput(new ResultWrapper("first", List.of("foo0", "foo1", "foo2", "foo3"))), - new GetFoosOutput(new ResultWrapper("second", List.of("foo0", "foo1", "foo2", "foo3"))), - new GetFoosOutput(new ResultWrapper("third", List.of("foo0", "foo1")))); - assertThat(results, contains(expectedResult.toArray())); - } - - @Test - void paginatorStopsOnFalsePredicate() { - var input = GetFoosInput.builder().maxResults(4).build(); - var paginator = AsyncPaginator.paginate(input, new TestOperationPaginated(), mockClient::getFoosAsync); - - List results = new ArrayList<>(); - // Block and wait on results - paginator.forEach(r -> { - results.add(r); - return !"second".equals(r.result().nextToken()); - }).join(); - - var expectedResult = List.of( - new GetFoosOutput(new ResultWrapper("first", List.of("foo0", "foo1", "foo2", "foo3"))), - new GetFoosOutput(new ResultWrapper("second", List.of("foo0", "foo1", "foo2", "foo3")))); - assertThat(results, contains(expectedResult.toArray())); - } - - private static final class PaginationTestSubscriber implements Flow.Subscriber { - private Flow.Subscription subscription; - private final BlockingQueue results = new LinkedBlockingQueue<>(); - private final CompletableFuture> future = new CompletableFuture<>(); - - private List results() { - return future.join(); - } - - @Override - public void onSubscribe(Flow.Subscription subscription) { - this.subscription = subscription; - // Request a result - subscription.request(1); - } - - @Override - public void onNext(GetFoosOutput item) { - results.add(item); - // request another - subscription.request(1); - } - - @Override - public void onError(Throwable throwable) { - // Do nothing - } - - @Override - public void onComplete() { - future.complete(results.stream().toList()); - } - } -} diff --git a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/pagination/MockClient.java b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/pagination/MockClient.java index b83465e60..0aa71f136 100644 --- a/client/client-core/src/test/java/software/amazon/smithy/java/client/core/pagination/MockClient.java +++ b/client/client-core/src/test/java/software/amazon/smithy/java/client/core/pagination/MockClient.java @@ -9,9 +9,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import software.amazon.smithy.java.client.core.RequestOverrideConfig; import software.amazon.smithy.java.client.core.pagination.models.GetFoosInput; import software.amazon.smithy.java.client.core.pagination.models.GetFoosOutput; @@ -19,16 +16,11 @@ final class MockClient { private static final List tokens = List.of("first", "second", "third", "final"); - private final Executor executor = CompletableFuture.delayedExecutor(3, TimeUnit.MILLISECONDS); private final Iterator tokenIterator = tokens.iterator(); private String nextToken = null; private boolean completed = false; public GetFoosOutput getFoosSync(GetFoosInput in, RequestOverrideConfig override) { - return getFoosAsync(in, override).join(); - } - - public CompletableFuture getFoosAsync(GetFoosInput in, RequestOverrideConfig override) { if (!Objects.equals(nextToken, in.nextToken())) { throw new IllegalArgumentException( "Next token " + nextToken + " does not match expected " + in.nextToken()); @@ -42,7 +34,6 @@ public CompletableFuture getFoosAsync(GetFoosInput in, RequestOve } completed = !tokenIterator.hasNext(); nextToken = tokenIterator.hasNext() ? tokenIterator.next() : null; - var output = new GetFoosOutput(new ResultWrapper(nextToken, foos)); - return CompletableFuture.supplyAsync(() -> output, executor); + return new GetFoosOutput(new ResultWrapper(nextToken, foos)); } } diff --git a/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingClientProtocol.java b/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingClientProtocol.java index 6264409a1..b9539537e 100644 --- a/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingClientProtocol.java +++ b/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingClientProtocol.java @@ -6,7 +6,6 @@ package software.amazon.smithy.java.client.http.binding; import java.net.URI; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.client.http.HttpClientProtocol; import software.amazon.smithy.java.client.http.HttpErrorDeserializer; import software.amazon.smithy.java.context.Context; @@ -87,7 +86,7 @@ public HttpRequest } @Override - public CompletableFuture deserializeResponse( + public O deserializeResponse( ApiOperation operation, Context context, TypeRegistry typeRegistry, @@ -95,9 +94,7 @@ public CompletableF HttpResponse response ) { if (!isSuccess(operation, context, response)) { - return createError(operation, context, typeRegistry, request, response).thenApply(e -> { - throw e; - }); + throw createError(operation, context, typeRegistry, request, response); } LOGGER.trace("Deserializing successful response with {}", getClass().getName()); @@ -113,13 +110,10 @@ public CompletableF deser.eventDecoderFactory(getEventDecoderFactory(o)); } - return deser - .deserialize() - .thenApply(ignore -> { - O output = outputBuilder.errorCorrection().build(); - LOGGER.trace("Successfully built {} from HTTP response with {}", output, getClass().getName()); - return output; - }); + deser.deserialize(); + O output = outputBuilder.errorCorrection().build(); + LOGGER.trace("Successfully built {} from HTTP response with {}", output, getClass().getName()); + return output; } /** @@ -144,14 +138,13 @@ protected boolean isSuccess(ApiOperation operation, Context context, HttpR * @param response HTTP response to deserialize. * @return Returns the deserialized error. */ - protected CompletableFuture createError( - ApiOperation operation, - Context context, - TypeRegistry typeRegistry, - HttpRequest request, - HttpResponse response - ) { + protected CallException createError( + ApiOperation operation, + Context context, + TypeRegistry typeRegistry, + HttpRequest request, + HttpResponse response + ) { return getErrorDeserializer(context).createError(context, operation.schema().id(), typeRegistry, response); } } diff --git a/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorFactory.java b/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorFactory.java index f2d68fc66..7a94f3558 100644 --- a/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorFactory.java +++ b/client/client-http-binding/src/main/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorFactory.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.client.http.binding; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.client.http.HttpErrorDeserializer; import software.amazon.smithy.java.context.Context; import software.amazon.smithy.java.core.error.ModeledException; @@ -33,17 +32,17 @@ public HttpBindingErrorFactory(HttpBinding httpBinding) { } @Override - public CompletableFuture createError( + public ModeledException createError( Context context, Codec codec, HttpResponse response, ShapeBuilder builder ) { - return httpBinding.responseDeserializer() + httpBinding.responseDeserializer() .payloadCodec(codec) .errorShapeBuilder(builder) .response(response) - .deserialize() - .thenApply(ignore -> builder.errorCorrection().build()); + .deserialize(); + return builder.errorCorrection().build(); } } diff --git a/client/client-http-binding/src/test/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorDeserializerTest.java b/client/client-http-binding/src/test/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorDeserializerTest.java index ba449e738..62ad968cc 100644 --- a/client/client-http-binding/src/test/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorDeserializerTest.java +++ b/client/client-http-binding/src/test/java/software/amazon/smithy/java/client/http/binding/HttpBindingErrorDeserializerTest.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.client.http.AmznErrorHeaderExtractor; import software.amazon.smithy.java.client.http.HttpErrorDeserializer; @@ -38,7 +37,7 @@ public class HttpBindingErrorDeserializerTest { private static final ShapeId OPERATION = ShapeId.from("com.foo#PutFoo"); @Test - public void deserializesErrorsWithHttpBindingsToo() throws Exception { + public void deserializesErrorsWithHttpBindingsToo() { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) @@ -51,20 +50,18 @@ public void deserializesErrorsWithHttpBindingsToo() throws Exception { .statusCode(400) .body(DataStream.ofString("{\"__type\": \"com.foo#Baz\"}")); var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result, instanceOf(Baz.class)); } @Test - public void usesGenericErrorWhenPayloadTypeIsUnknown() throws Exception { + public void usesGenericErrorWhenPayloadTypeIsUnknown() { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) .knownErrorFactory(new HttpBindingErrorFactory()) - .unknownErrorFactory( - (fault, message, response) -> CompletableFuture - .completedFuture(new CallException("Hi!", fault))) + .unknownErrorFactory((fault, message, response) -> new CallException("Hi!", fault)) .build(); var registry = TypeRegistry.builder() .putType(Baz.SCHEMA.id(), Baz.class, Baz.Builder::new) @@ -73,22 +70,20 @@ public void usesGenericErrorWhenPayloadTypeIsUnknown() throws Exception { .statusCode(400) .body(DataStream.ofString("{\"__type\": \"com.foo#SomeUnknownError\"}")); var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result, instanceOf(CallException.class)); assertThat(result.getMessage(), equalTo("Hi!")); } @Test - public void usesGenericErrorWhenHeaderTypeIsUnknown() throws Exception { + public void usesGenericErrorWhenHeaderTypeIsUnknown() { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) .knownErrorFactory(new HttpBindingErrorFactory()) .headerErrorExtractor(new AmznErrorHeaderExtractor()) - .unknownErrorFactory( - (fault, message, response) -> CompletableFuture - .completedFuture(new CallException("Hi!", fault))) + .unknownErrorFactory((fault, message, response) -> new CallException("Hi!", fault)) .build(); var registry = TypeRegistry.builder() .putType(Baz.SCHEMA.id(), Baz.class, Baz.Builder::new) @@ -104,7 +99,7 @@ public void usesGenericErrorWhenHeaderTypeIsUnknown() throws Exception { List.of("com.foo#SomeUnknownError")))) .body(DataStream.ofString("{}")); var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result, instanceOf(CallException.class)); assertThat(result.getMessage(), equalTo("Hi!")); diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpClientDataStream.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpClientDataStream.java deleted file mode 100644 index 1270ab06e..000000000 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpClientDataStream.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.client.http; - -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Flow; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import software.amazon.smithy.java.io.datastream.DataStream; - -/** - * This class defers turning the HTTP client's response publisher into an adapted publisher unless required. - * - *

This class avoids needing to use multiple intermediate adapters to go from a flow that publishes a list of - * byte buffers to a flow that publishes single byte buffer. Instead, it directly implements asByteBuffer and - * asInputStream to use a more direct integration from the HTTP client library. - */ -record HttpClientDataStream( - Flow.Publisher> httpPublisher, - long contentLength, - String contentType) implements DataStream { - @Override - public boolean isReplayable() { - return false; - } - - @Override - public CompletableFuture asByteBuffer() { - var p = java.net.http.HttpResponse.BodySubscribers.ofByteArray(); - httpPublisher.subscribe(p); - return p.getBody().thenApply(ByteBuffer::wrap).toCompletableFuture(); - } - - @Override - public CompletableFuture asInputStream() { - var p = java.net.http.HttpResponse.BodySubscribers.ofInputStream(); - httpPublisher.subscribe(p); - return p.getBody().toCompletableFuture(); - } - - @Override - public void subscribe(Flow.Subscriber subscriber) { - // Adapt the "Flow.Subscriber" to "Flow.Subscriber". - httpPublisher.subscribe(new BbListToBbSubscriber(subscriber)); - } - - private static final class BbListToBbSubscriber implements Flow.Subscriber> { - private final Flow.Subscriber subscriber; - - BbListToBbSubscriber(Flow.Subscriber subscriber) { - this.subscriber = subscriber; - } - - private Flow.Subscription upstreamSubscription; - private final Queue queue = new ConcurrentLinkedQueue<>(); - private final AtomicLong demand = new AtomicLong(0); - private final AtomicBoolean senderFinished = new AtomicBoolean(false); - - @Override - public void onSubscribe(Flow.Subscription subscription) { - upstreamSubscription = subscription; - - subscriber.onSubscribe(new Flow.Subscription() { - @Override - public void request(long n) { - demand.addAndGet(n); - drainAndRequest(); - } - - @Override - public void cancel() { - upstreamSubscription.cancel(); - } - }); - } - - @Override - public void onError(Throwable throwable) { - subscriber.onError(throwable); - } - - @Override - public void onNext(List item) { - queue.addAll(item); - drainAndRequest(); - } - - @Override - public void onComplete() { - // The sender is done sending us bytes, so when our queue is empty, emit onComplete downstream. - senderFinished.set(true); - drain(); - } - - private void drain() { - try { - while (!queue.isEmpty() && demand.get() > 0) { - ByteBuffer buffer = queue.poll(); - if (buffer != null) { - subscriber.onNext(buffer); - demand.decrementAndGet(); - } - } - // When we have no more buffered BBs and the sender has signaled they're done, then complete downstream. - if (queue.isEmpty() && senderFinished.get()) { - subscriber.onComplete(); - } - } catch (Exception e) { - subscriber.onError(e); - } - } - - private void drainAndRequest() { - drain(); - - var currentDemand = demand.get(); - if (currentDemand > 0 && queue.isEmpty() && !senderFinished.get()) { - upstreamSubscription.request(currentDemand); - } - } - } -} diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpErrorDeserializer.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpErrorDeserializer.java index 57cdca183..36b903428 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpErrorDeserializer.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpErrorDeserializer.java @@ -7,7 +7,6 @@ import java.nio.ByteBuffer; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.context.Context; import software.amazon.smithy.java.core.error.CallException; import software.amazon.smithy.java.core.error.ErrorFault; @@ -55,11 +54,7 @@ public interface HeaderErrorExtractor { */ @FunctionalInterface public interface UnknownErrorFactory { - CompletableFuture createError( - ErrorFault fault, - String message, - HttpResponse response - ); + CallException createError(ErrorFault fault, String message, HttpResponse response); } /** @@ -76,7 +71,7 @@ public interface KnownErrorFactory { * @param builder Builder to populate and build. * @return the created error. */ - CompletableFuture createError( + ModeledException createError( Context context, Codec codec, HttpResponse response, @@ -99,7 +94,7 @@ CompletableFuture createError( * @param builder Builder to populate and build. * @return the created error. */ - default CompletableFuture createErrorFromDocument( + default ModeledException createErrorFromDocument( Context context, Codec codec, HttpResponse response, @@ -130,24 +125,28 @@ public ShapeId resolveId(HttpResponse response, String serviceNamespace, TypeReg }; // Throws an ApiException. - private static final UnknownErrorFactory DEFAULT_UNKNOWN_FACTORY = (fault, message, response) -> CompletableFuture - .completedFuture(new CallException(message, fault)); + private static final UnknownErrorFactory DEFAULT_UNKNOWN_FACTORY = + (fault, message, response) -> new CallException(message, fault); // Deserializes without HTTP bindings. private static final KnownErrorFactory DEFAULT_KNOWN_FACTORY = new KnownErrorFactory() { @Override - public CompletableFuture createError( + public ModeledException createError( Context context, Codec codec, HttpResponse response, ShapeBuilder builder ) { - return createDataStream(response).asByteBuffer() - .thenApply(bytes -> codec.deserializeShape(bytes, builder)); + try { + ByteBuffer bytes = createDataStream(response).waitForByteBuffer(); + return codec.deserializeShape(bytes, builder); + } catch (Exception e) { + throw new RuntimeException("Failed to deserialize error", e); + } } @Override - public CompletableFuture createErrorFromDocument( + public ModeledException createErrorFromDocument( Context context, Codec codec, HttpResponse response, @@ -156,8 +155,7 @@ public CompletableFuture createErrorFromDocument( ShapeBuilder builder ) { parsedDocument.deserializeInto(builder); - var result = builder.errorCorrection().build(); - return CompletableFuture.completedFuture(result); + return builder.errorCorrection().build(); } }; @@ -185,7 +183,7 @@ public static Builder builder() { return new Builder(); } - public CompletableFuture createError( + public CallException createError( Context context, ShapeId operation, TypeRegistry typeRegistry, @@ -219,7 +217,7 @@ private static DataStream createDataStream(HttpResponse response) { return DataStream.ofPublisher(response.body(), response.contentType(), response.contentLength(-1)); } - private CompletableFuture makeErrorFromHeader( + private CallException makeErrorFromHeader( Context context, ShapeId operation, TypeRegistry typeRegistry, @@ -237,7 +235,7 @@ private CompletableFuture makeErrorFromHeader( } } - private static CompletableFuture makeErrorFromPayload( + private static CallException makeErrorFromPayload( Context context, Codec codec, KnownErrorFactory knownErrorFactory, @@ -247,28 +245,33 @@ private static CompletableFuture makeErrorFromPayload( HttpResponse response, DataStream content ) { - // Read the payload into a JSON document so we can efficiently find __type and then directly - // deserialize the document into the identified builder. - return content.asByteBuffer().thenCompose(buffer -> { + try { + // Read the payload into a JSON document so we can efficiently find __type and then directly + // deserialize the document into the identified builder. + ByteBuffer buffer = content.waitForByteBuffer(); + if (buffer.remaining() > 0) { - try { - var document = codec.createDeserializer(buffer).readDocument(); - var id = document.discriminator(); - var builder = typeRegistry.createBuilder(id, ModeledException.class); - if (builder != null) { - return knownErrorFactory - .createErrorFromDocument(context, codec, response, buffer, document, builder) - .thenApply(e -> e); - } - // ignore parsing errors here if the service is returning garbage. - } catch (SerializationException | DiscriminatorException ignored) {} + var document = codec.createDeserializer(buffer).readDocument(); + var id = document.discriminator(); + var builder = typeRegistry.createBuilder(id, ModeledException.class); + if (builder != null) { + return knownErrorFactory.createErrorFromDocument( + context, + codec, + response, + buffer, + document, + builder); + } } + } catch (SerializationException | DiscriminatorException ignored) { + // Ignore parsing errors here if the service is returning garbage + } - return createErrorFromHints(operationId, response, unknownErrorFactory); - }); + return createErrorFromHints(operationId, response, unknownErrorFactory); } - private static CompletableFuture createErrorFromHints( + private static CallException createErrorFromHints( ShapeId operationId, HttpResponse response, UnknownErrorFactory unknownErrorFactory diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java index 910c7944a..1dfcf1a63 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java @@ -5,14 +5,12 @@ package software.amazon.smithy.java.client.http; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpConnectTimeoutException; -import java.nio.ByteBuffer; import java.time.Duration; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Flow; import software.amazon.smithy.java.client.core.ClientTransport; import software.amazon.smithy.java.client.core.ClientTransportFactory; import software.amazon.smithy.java.client.core.MessageExchange; @@ -24,6 +22,7 @@ import software.amazon.smithy.java.http.api.HttpResponse; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.io.ByteBufferUtils; +import software.amazon.smithy.java.io.datastream.DataStream; import software.amazon.smithy.java.logging.InternalLogger; /** @@ -32,7 +31,7 @@ */ public class JavaHttpClientTransport implements ClientTransport { - private static URI DUMMY_URI = URI.create("http://localhost"); + private static final URI DUMMY_URI = URI.create("http://localhost"); private static final InternalLogger LOGGER = InternalLogger.getLogger(JavaHttpClientTransport.class); private final HttpClient client; @@ -103,7 +102,7 @@ public MessageExchange messageExchange() { } @Override - public CompletableFuture send(Context context, HttpRequest request) { + public HttpResponse send(Context context, HttpRequest request) { return sendRequest(createJavaRequest(context, request)); } @@ -141,32 +140,35 @@ private java.net.http.HttpRequest createJavaRequest(Context context, HttpRequest return httpRequestBuilder.build(); } - private CompletableFuture sendRequest(java.net.http.HttpRequest request) { - return client.sendAsync(request, java.net.http.HttpResponse.BodyHandlers.ofPublisher()) - .thenApply(this::createSmithyResponse) - .exceptionally(e -> { - if (e instanceof HttpConnectTimeoutException) { - throw new ConnectTimeoutException(e); - } - // The client pipeline also does this remapping, but to adhere to the required contract of - // ClientTransport, we remap here too if needed. - throw ClientTransport.remapExceptions(e); - }); + private HttpResponse sendRequest(java.net.http.HttpRequest request) { + try { + var res = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofInputStream()); + return createSmithyResponse(res); + } catch (IOException | InterruptedException | RuntimeException e) { + if (e instanceof HttpConnectTimeoutException) { + throw new ConnectTimeoutException(e); + } + // The client pipeline also does this remapping, but to adhere to the required contract of + // ClientTransport, we remap here too if needed. + throw ClientTransport.remapExceptions(e); + } } - private HttpResponse createSmithyResponse(java.net.http.HttpResponse>> response) { + private HttpResponse createSmithyResponse(java.net.http.HttpResponse response) { var headerMap = response.headers().map(); LOGGER.trace("Got response: {}; headers: {}", response, headerMap); var headers = HttpHeaders.of(headerMap); var length = headers.contentLength(); var adaptedLength = length == null ? -1 : length; + var contentType = headers.contentType(); + var body = DataStream.ofInputStream(response.body(), contentType, adaptedLength); return HttpResponse.builder() .httpVersion(javaToSmithyVersion(response.version())) .statusCode(response.statusCode()) .headers(headers) - .body(new HttpClientDataStream(response.body(), adaptedLength, headers.contentType())) + .body(body) .build(); } diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSigner.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSigner.java index b2d3b087d..d0992b0ba 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSigner.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSigner.java @@ -7,7 +7,6 @@ import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.Signer; import software.amazon.smithy.java.auth.api.identity.ApiKeyIdentity; import software.amazon.smithy.java.context.Context; @@ -24,11 +23,7 @@ final class HttpApiKeyAuthSigner implements Signer private HttpApiKeyAuthSigner() {} @Override - public CompletableFuture sign( - HttpRequest request, - ApiKeyIdentity identity, - Context properties - ) { + public HttpRequest sign(HttpRequest request, ApiKeyIdentity identity, Context properties) { var name = properties.expect(HttpApiKeyAuthScheme.NAME); return switch (properties.expect(HttpApiKeyAuthScheme.IN)) { case HEADER -> { @@ -43,7 +38,7 @@ public CompletableFuture sign( if (existing != null) { LOGGER.debug("Replaced header value for {}", name); } - yield CompletableFuture.completedFuture(request.toBuilder().headers(HttpHeaders.of(updated)).build()); + yield request.toBuilder().headers(HttpHeaders.of(updated)).build(); } case QUERY -> { var uriBuilder = URIBuilder.of(request.uri()); @@ -53,8 +48,7 @@ public CompletableFuture sign( var existingQuery = request.uri().getQuery(); addExistingQueryParams(stringBuilder, existingQuery, name); queryBuilder.write(stringBuilder); - yield CompletableFuture.completedFuture( - request.toBuilder().uri(uriBuilder.query(stringBuilder.toString()).build()).build()); + yield request.toBuilder().uri(uriBuilder.query(stringBuilder.toString()).build()).build(); } }; } diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSigner.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSigner.java index c2159dbbf..5d271e257 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSigner.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSigner.java @@ -9,7 +9,6 @@ import java.util.Base64; import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.Signer; import software.amazon.smithy.java.auth.api.identity.LoginIdentity; import software.amazon.smithy.java.context.Context; @@ -26,11 +25,7 @@ final class HttpBasicAuthSigner implements Signer { private HttpBasicAuthSigner() {} @Override - public CompletableFuture sign( - HttpRequest request, - LoginIdentity identity, - Context properties - ) { + public HttpRequest sign(HttpRequest request, LoginIdentity identity, Context properties) { var identityString = identity.username() + ":" + identity.password(); var base64Value = Base64.getEncoder().encodeToString(identityString.getBytes(StandardCharsets.UTF_8)); var headers = new LinkedHashMap<>(request.headers().map()); @@ -38,6 +33,6 @@ public CompletableFuture sign( if (existing != null) { LOGGER.debug("Replaced existing Authorization header value."); } - return CompletableFuture.completedFuture(request.toBuilder().headers(HttpHeaders.of(headers)).build()); + return request.toBuilder().headers(HttpHeaders.of(headers)).build(); } } diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSigner.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSigner.java index db7f73fad..54f28b92d 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSigner.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSigner.java @@ -7,7 +7,6 @@ import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.Signer; import software.amazon.smithy.java.auth.api.identity.TokenIdentity; import software.amazon.smithy.java.context.Context; @@ -24,16 +23,12 @@ final class HttpBearerAuthSigner implements Signer { private HttpBearerAuthSigner() {} @Override - public CompletableFuture sign( - HttpRequest request, - TokenIdentity identity, - Context properties - ) { + public HttpRequest sign(HttpRequest request, TokenIdentity identity, Context properties) { var headers = new LinkedHashMap<>(request.headers().map()); var existing = headers.put(AUTHORIZATION_HEADER, List.of(SCHEME + " " + identity.token())); if (existing != null) { LOGGER.debug("Replaced existing Authorization header value."); } - return CompletableFuture.completedFuture(request.toBuilder().headers(HttpHeaders.of(headers)).build()); + return request.toBuilder().headers(HttpHeaders.of(headers)).build(); } } diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpDigestAuthSigner.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpDigestAuthSigner.java index e01f9061d..d7beaca08 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpDigestAuthSigner.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/auth/HttpDigestAuthSigner.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.client.http.auth; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.Signer; import software.amazon.smithy.java.auth.api.identity.LoginIdentity; import software.amazon.smithy.java.context.Context; @@ -20,11 +19,7 @@ final class HttpDigestAuthSigner implements Signer { private HttpDigestAuthSigner() {} @Override - public CompletableFuture sign( - HttpRequest request, - LoginIdentity identity, - Context properties - ) { + public HttpRequest sign(HttpRequest request, LoginIdentity identity, Context properties) { throw new UnsupportedOperationException(); } } diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/plugins/UserAgentPlugin.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/plugins/UserAgentPlugin.java index 14e53cd2f..2e816f732 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/plugins/UserAgentPlugin.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/plugins/UserAgentPlugin.java @@ -37,7 +37,7 @@ public void configureClient(ClientConfig.Builder config) { /** * Adds a default User-Agent header if none is set. * - *

The agent is in the form of {@code smithy-java/0.1 ua/2.1 os/macos#14.6.1Lang/java#17.0.12 m/a,b}, where + *

The agent is in the form of {@code smithy-java/0.1 ua/2.1 os/macos#14.6.1Lang/java#21.0.12 m/a,b}, where * "m/a,b" are feature IDs set via {@link CallContext#FEATURE_IDS}. * *

A pair of "app/{id}" is added if {@link CallContext#APPLICATION_ID} is set, or a value is set in the diff --git a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpClientDataStreamTest.java b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpClientDataStreamTest.java deleted file mode 100644 index e282156c4..000000000 --- a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpClientDataStreamTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.client.http; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Flow; -import java.util.concurrent.SubmissionPublisher; -import org.junit.jupiter.api.Test; - -public class HttpClientDataStreamTest { - - private static List> createCannedBuffers() { - return List.of( - List.of(ByteBuffer.wrap("{\"hi\":".getBytes(StandardCharsets.UTF_8))), - List.of( - ByteBuffer.wrap("[1, ".getBytes(StandardCharsets.UTF_8)), - ByteBuffer.wrap("2]".getBytes(StandardCharsets.UTF_8))), - List.of(ByteBuffer.wrap("}".getBytes(StandardCharsets.UTF_8)))); - } - - private static final class CannedPublisher extends SubmissionPublisher> { - void pushData(List> data) { - data.forEach(this::submit); - close(); - } - } - - @Test - public void convertsToBb() throws Exception { - var httpPublisher = new CannedPublisher(); - var ds = new HttpClientDataStream(httpPublisher, 13, "application/json"); - assertThat(ds.contentType(), equalTo("application/json")); - assertThat(ds.contentLength(), equalTo(13L)); - - var cf = ds.asByteBuffer(); - httpPublisher.pushData(createCannedBuffers()); - - var bb = cf.get(); - assertThat(bb.remaining(), equalTo(13)); - assertThat(new String(bb.array(), StandardCharsets.UTF_8), equalTo("{\"hi\":[1, 2]}")); - } - - @Test - public void convertsToInputStream() throws Exception { - var httpPublisher = new CannedPublisher(); - var ds = new HttpClientDataStream(httpPublisher, 13, "application/json"); - var cf = ds.asInputStream(); - httpPublisher.pushData(createCannedBuffers()); - - var is = cf.get(); - assertThat(new String(is.readAllBytes(), StandardCharsets.UTF_8), equalTo("{\"hi\":[1, 2]}")); - } - - @Test - public void convertsToPublisher() throws Exception { - var httpPublisher = new CannedPublisher(); - var ds = new HttpClientDataStream(httpPublisher, 13, "application/json"); - - var collector = new CollectingSubscriber(); - ds.subscribe(collector); - httpPublisher.pushData(createCannedBuffers()); - var results = collector.getResult().get(); - - assertThat(results, equalTo("{\"hi\":[1, 2]}")); - } - - public static final class CollectingSubscriber implements Flow.Subscriber { - private final List buffers = Collections.synchronizedList(new ArrayList<>()); - private final CompletableFuture result = new CompletableFuture<>(); - private Flow.Subscription subscription; - - @Override - public void onSubscribe(Flow.Subscription subscription) { - this.subscription = subscription; - subscription.request(Long.MAX_VALUE); - } - - @Override - public void onNext(ByteBuffer item) { - buffers.add(new String(item.array(), StandardCharsets.UTF_8)); - } - - @Override - public void onError(Throwable throwable) { - result.completeExceptionally(throwable); - } - - @Override - public void onComplete() { - StringBuilder builder = new StringBuilder(); - for (String buffer : buffers) { - builder.append(buffer); - } - result.complete(builder.toString()); - } - - public CompletableFuture getResult() { - return result; - } - } -} diff --git a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpClientProtocolTest.java b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpClientProtocolTest.java index fc1107ade..d90a75e40 100644 --- a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpClientProtocolTest.java +++ b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpClientProtocolTest.java @@ -9,7 +9,6 @@ import static org.hamcrest.Matchers.equalTo; import java.net.URI; -import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.client.core.endpoint.Endpoint; import software.amazon.smithy.java.context.Context; @@ -42,7 +41,7 @@ public HttpRequest @Override public CompletableFuture deserializeResponse( + O extends SerializableStruct> O deserializeResponse( ApiOperation operation, Context context, TypeRegistry errorRegistry, diff --git a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpErrorDeserializerTest.java b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpErrorDeserializerTest.java index 8b3b790de..feb5c4318 100644 --- a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpErrorDeserializerTest.java +++ b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/HttpErrorDeserializerTest.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -41,7 +40,7 @@ public class HttpErrorDeserializerTest { @ParameterizedTest @MethodSource("genericErrorCases") - public void createErrorFromHints(int status, String payload, String message) throws Exception { + public void createErrorFromHints(int status, String payload, String message) { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) @@ -55,7 +54,7 @@ public void createErrorFromHints(int status, String payload, String message) thr HttpHeaders.of(Map.of("content-length", List.of(Integer.toString(payload.length()))))); } var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result.getMessage(), containsString(message)); } @@ -77,7 +76,7 @@ static List genericErrorCases() { } @Test - public void deserializesIntoErrorBasedOnHeaders() throws Exception { + public void deserializesIntoErrorBasedOnHeaders() { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) @@ -97,13 +96,13 @@ public void deserializesIntoErrorBasedOnHeaders() throws Exception { List.of(Baz.SCHEMA.id().toString())))) .body(DataStream.ofString("{}")); var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result, instanceOf(Baz.class)); } @Test - public void deserializesUsingDocumentViaPayloadWithNoContentLength() throws Exception { + public void deserializesUsingDocumentViaPayloadWithNoContentLength() { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) @@ -116,19 +115,17 @@ public void deserializesUsingDocumentViaPayloadWithNoContentLength() throws Exce .statusCode(400) .body(DataStream.ofString("{\"__type\": \"com.foo#Baz\"}")); var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result, instanceOf(Baz.class)); } @Test - public void usesGenericErrorWhenPayloadTypeIsUnknown() throws Exception { + public void usesGenericErrorWhenPayloadTypeIsUnknown() { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) - .unknownErrorFactory( - (fault, message, response) -> CompletableFuture - .completedFuture(new CallException("Hi!", fault))) + .unknownErrorFactory((fault, message, response) -> new CallException("Hi!", fault)) .build(); var registry = TypeRegistry.builder() .putType(Baz.SCHEMA.id(), Baz.class, Baz.Builder::new) @@ -137,21 +134,19 @@ public void usesGenericErrorWhenPayloadTypeIsUnknown() throws Exception { .statusCode(400) .body(DataStream.ofString("{\"__type\": \"com.foo#SomeUnknownError\"}")); var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result, instanceOf(CallException.class)); assertThat(result.getMessage(), equalTo("Hi!")); } @Test - public void usesGenericErrorWhenHeaderTypeIsUnknown() throws Exception { + public void usesGenericErrorWhenHeaderTypeIsUnknown() { var deserializer = HttpErrorDeserializer.builder() .codec(CODEC) .serviceId(SERVICE) .headerErrorExtractor(new AmznErrorHeaderExtractor()) - .unknownErrorFactory( - (fault, message, response) -> CompletableFuture - .completedFuture(new CallException("Hi!", fault))) + .unknownErrorFactory((fault, message, response) -> new CallException("Hi!", fault)) .build(); var registry = TypeRegistry.builder() .putType(Baz.SCHEMA.id(), Baz.class, Baz.Builder::new) @@ -167,7 +162,7 @@ public void usesGenericErrorWhenHeaderTypeIsUnknown() throws Exception { List.of("com.foo#SomeUnknownError")))) .body(DataStream.ofString("{}")); var response = responseBuilder.build(); - var result = deserializer.createError(Context.create(), OPERATION, registry, response).get(); + var result = deserializer.createError(Context.create(), OPERATION, registry, response); assertThat(result, instanceOf(CallException.class)); assertThat(result.getMessage(), equalTo("Hi!")); diff --git a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSignerTest.java b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSignerTest.java index 0ae6eeb9c..be16cb576 100644 --- a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSignerTest.java +++ b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpApiKeyAuthSignerTest.java @@ -9,7 +9,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import java.net.URI; -import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.auth.api.identity.ApiKeyIdentity; import software.amazon.smithy.java.context.Context; @@ -27,89 +26,89 @@ public class HttpApiKeyAuthSignerTest { .build(); @Test - void testApiKeyAuthSignerAddsHeaderNoScheme() throws ExecutionException, InterruptedException { + void testApiKeyAuthSignerAddsHeaderNoScheme() { var authProperties = Context.create(); authProperties.put(HttpApiKeyAuthScheme.IN, HttpApiKeyAuthTrait.Location.HEADER); authProperties.put(HttpApiKeyAuthScheme.NAME, "x-api-key"); - var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties).get(); + var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties); var authHeader = signedRequest.headers().firstValue("x-api-key"); assertEquals(authHeader, API_KEY); } @Test - void testApiKeyAuthSignerAddsHeaderParamWithCustomScheme() throws ExecutionException, InterruptedException { + void testApiKeyAuthSignerAddsHeaderParamWithCustomScheme() { var authProperties = Context.create(); authProperties.put(HttpApiKeyAuthScheme.IN, HttpApiKeyAuthTrait.Location.HEADER); authProperties.put(HttpApiKeyAuthScheme.NAME, "x-api-key"); authProperties.put(HttpApiKeyAuthScheme.SCHEME, "SCHEME"); - var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties).get(); + var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties); var authHeader = signedRequest.headers().firstValue("x-api-key"); assertEquals(authHeader, "SCHEME " + API_KEY); } @Test - void testOverwritesExistingHeader() throws ExecutionException, InterruptedException { + void testOverwritesExistingHeader() { var authProperties = Context.create(); authProperties.put(HttpApiKeyAuthScheme.IN, HttpApiKeyAuthTrait.Location.HEADER); authProperties.put(HttpApiKeyAuthScheme.NAME, "x-api-key"); authProperties.put(HttpApiKeyAuthScheme.SCHEME, "SCHEME"); var updateRequest = TEST_REQUEST.toBuilder().withAddedHeader("x-api-key", "foo").build(); - var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(updateRequest, TEST_IDENTITY, authProperties).get(); + var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(updateRequest, TEST_IDENTITY, authProperties); var authHeader = signedRequest.headers().firstValue("x-api-key"); assertEquals(authHeader, "SCHEME " + API_KEY); } @Test - void testApiKeyAuthSignerAddsQueryParam() throws ExecutionException, InterruptedException { + void testApiKeyAuthSignerAddsQueryParam() { var authProperties = Context.create(); authProperties.put(HttpApiKeyAuthScheme.IN, HttpApiKeyAuthTrait.Location.QUERY); authProperties.put(HttpApiKeyAuthScheme.NAME, "apiKey"); - var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties).get(); + var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties); var queryParam = signedRequest.uri().getQuery(); assertNotNull(queryParam); assertEquals(queryParam, "apiKey=my-api-key"); } @Test - void testApiKeyAuthSignerAddsQueryParamIgnoresScheme() throws ExecutionException, InterruptedException { + void testApiKeyAuthSignerAddsQueryParamIgnoresScheme() { var authProperties = Context.create(); authProperties.put(HttpApiKeyAuthScheme.IN, HttpApiKeyAuthTrait.Location.QUERY); authProperties.put(HttpApiKeyAuthScheme.NAME, "apiKey"); authProperties.put(HttpApiKeyAuthScheme.SCHEME, "SCHEME"); - var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties).get(); + var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(TEST_REQUEST, TEST_IDENTITY, authProperties); var queryParam = signedRequest.uri().getQuery(); assertNotNull(queryParam); assertEquals(queryParam, "apiKey=my-api-key"); } @Test - void testApiKeyAuthSignerAddsQueryParamsAppendsToExisting() throws ExecutionException, InterruptedException { + void testApiKeyAuthSignerAddsQueryParamsAppendsToExisting() { var authProperties = Context.create(); authProperties.put(HttpApiKeyAuthScheme.IN, HttpApiKeyAuthTrait.Location.QUERY); authProperties.put(HttpApiKeyAuthScheme.NAME, "apiKey"); var updatedRequest = TEST_REQUEST.toBuilder().uri(URI.create("https://www.example.com?x=1")).build(); - var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(updatedRequest, TEST_IDENTITY, authProperties).get(); + var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(updatedRequest, TEST_IDENTITY, authProperties); var queryParam = signedRequest.uri().getQuery(); assertNotNull(queryParam); assertEquals(queryParam, "x=1&apiKey=my-api-key"); } @Test - void testOverwritesExistingQuery() throws ExecutionException, InterruptedException { + void testOverwritesExistingQuery() { var authProperties = Context.create(); authProperties.put(HttpApiKeyAuthScheme.IN, HttpApiKeyAuthTrait.Location.QUERY); authProperties.put(HttpApiKeyAuthScheme.NAME, "apiKey"); var updatedRequest = TEST_REQUEST.toBuilder().uri(URI.create("https://www.example.com?x=1&apiKey=foo")).build(); - var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(updatedRequest, TEST_IDENTITY, authProperties).get(); + var signedRequest = HttpApiKeyAuthSigner.INSTANCE.sign(updatedRequest, TEST_IDENTITY, authProperties); var queryParam = signedRequest.uri().getQuery(); assertNotNull(queryParam); assertEquals(queryParam, "x=1&apiKey=my-api-key"); diff --git a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSignerTest.java b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSignerTest.java index a96842d7c..fc483d1ce 100644 --- a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSignerTest.java +++ b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBasicAuthSignerTest.java @@ -12,7 +12,6 @@ import java.util.Base64; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.auth.api.identity.LoginIdentity; import software.amazon.smithy.java.context.Context; @@ -22,7 +21,7 @@ public class HttpBasicAuthSignerTest { @Test - void testBasicAuthSigner() throws ExecutionException, InterruptedException { + void testBasicAuthSigner() { var username = "username"; var password = "password"; var testIdentity = LoginIdentity.create(username, password); @@ -34,13 +33,13 @@ void testBasicAuthSigner() throws ExecutionException, InterruptedException { var expectedHeader = "Basic " + Base64.getEncoder() .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); - var signedRequest = HttpBasicAuthSigner.INSTANCE.sign(request, testIdentity, Context.empty()).get(); + var signedRequest = HttpBasicAuthSigner.INSTANCE.sign(request, testIdentity, Context.empty()); var authHeader = signedRequest.headers().firstValue("authorization"); assertEquals(authHeader, expectedHeader); } @Test - void overwritesExistingHeader() throws ExecutionException, InterruptedException { + void overwritesExistingHeader() { var username = "username"; var password = "password"; var testIdentity = LoginIdentity.create(username, password); @@ -53,7 +52,7 @@ void overwritesExistingHeader() throws ExecutionException, InterruptedException var expectedHeader = "Basic " + Base64.getEncoder() .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); - var signedRequest = HttpBasicAuthSigner.INSTANCE.sign(request, testIdentity, Context.empty()).get(); + var signedRequest = HttpBasicAuthSigner.INSTANCE.sign(request, testIdentity, Context.empty()); var authHeader = signedRequest.headers().firstValue("authorization"); assertEquals(authHeader, expectedHeader); } diff --git a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSignerTest.java b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSignerTest.java index f572aee0f..c79078253 100644 --- a/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSignerTest.java +++ b/client/client-http/src/test/java/software/amazon/smithy/java/client/http/auth/HttpBearerAuthSignerTest.java @@ -10,7 +10,6 @@ import java.net.URI; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.auth.api.identity.TokenIdentity; import software.amazon.smithy.java.context.Context; @@ -20,7 +19,7 @@ public class HttpBearerAuthSignerTest { @Test - void testBearerAuthSigner() throws ExecutionException, InterruptedException { + void testBearerAuthSigner() { var tokenIdentity = TokenIdentity.create("token"); var request = HttpRequest.builder() .httpVersion(HttpVersion.HTTP_1_1) @@ -28,13 +27,13 @@ void testBearerAuthSigner() throws ExecutionException, InterruptedException { .uri(URI.create("https://www.example.com")) .build(); - var signedRequest = HttpBearerAuthSigner.INSTANCE.sign(request, tokenIdentity, Context.empty()).get(); + var signedRequest = HttpBearerAuthSigner.INSTANCE.sign(request, tokenIdentity, Context.empty()); var authHeader = signedRequest.headers().firstValue("authorization"); assertEquals(authHeader, "Bearer token"); } @Test - void overwritesExistingHeader() throws ExecutionException, InterruptedException { + void overwritesExistingHeader() { var tokenIdentity = TokenIdentity.create("token"); var request = HttpRequest.builder() .httpVersion(HttpVersion.HTTP_1_1) @@ -43,7 +42,7 @@ void overwritesExistingHeader() throws ExecutionException, InterruptedException .uri(URI.create("https://www.example.com")) .build(); - var signedRequest = HttpBearerAuthSigner.INSTANCE.sign(request, tokenIdentity, Context.empty()).get(); + var signedRequest = HttpBearerAuthSigner.INSTANCE.sign(request, tokenIdentity, Context.empty()); var authHeader = signedRequest.headers().firstValue("authorization"); assertEquals(authHeader, "Bearer token"); } diff --git a/client/client-mock-plugin/src/main/java/software/amazon/smithy/java/client/http/mock/MockPlugin.java b/client/client-mock-plugin/src/main/java/software/amazon/smithy/java/client/http/mock/MockPlugin.java index de518e735..9778d6bb6 100644 --- a/client/client-mock-plugin/src/main/java/software/amazon/smithy/java/client/http/mock/MockPlugin.java +++ b/client/client-mock-plugin/src/main/java/software/amazon/smithy/java/client/http/mock/MockPlugin.java @@ -12,7 +12,6 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.ServiceLoader; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.stream.Collectors; import software.amazon.smithy.java.client.core.ClientConfig; @@ -198,7 +197,7 @@ public HttpRequest setServiceEndpoint(HttpRequest request, Endpoint endpoint) { } @Override - public CompletableFuture deserializeResponse( + public O deserializeResponse( ApiOperation operation, Context context, TypeRegistry typeRegistry, @@ -216,7 +215,7 @@ public MessageExchange messageExchange() { } @Override - public CompletableFuture send(Context context, HttpRequest request) { + public HttpResponse send(Context context, HttpRequest request) { var currentRequest = context.expect(CURRENT_REQUEST); // Update the current context value with the potentially changed request. @@ -245,21 +244,34 @@ public CompletableFuture send(Context context, HttpRequest request } if (result instanceof MockedResult.Response res) { - return CompletableFuture.completedFuture(res.response()); + return res.response(); } else if (result instanceof MockedResult.Error err) { - return CompletableFuture.failedFuture(err.e()); - } else if (result instanceof MockedResult.Output o) { + var e = err.e(); + if (e instanceof RuntimeException re) { + throw re; + } else { + sneakyThrows(e); + } // ^ guaranteed to throw + } + + if (result instanceof MockedResult.Output o) { return replyWithMockOutput(currentRequest, o); - } else { - throw new IllegalStateException("Unknown result type: " + result.getClass().getName()); } + + throw new IllegalStateException("Unknown result type: " + result.getClass().getName()); + } + } + + @SuppressWarnings("unchecked") + private static E sneakyThrows(Throwable e) throws E { + Throwable cause = e.getCause(); + if (cause != null) { + throw (E) cause; } + throw (E) e; } - private CompletableFuture replyWithMockOutput( - CurrentRequest currentRequest, - MockedResult.Output output - ) { + private HttpResponse replyWithMockOutput(CurrentRequest currentRequest, MockedResult.Output output) { var cRequest = currentRequest.request().request(); var serverRequest = new software.amazon.smithy.java.server.core.HttpRequest( cRequest.headers(), @@ -276,20 +288,17 @@ private CompletableFuture replyWithMockOutput( var response = new software.amazon.smithy.java.server.core.HttpResponse(HttpHeaders.ofModifiable()); var job = new HttpJob(currentRequest.operation(), protocol, serverRequest, response); - CompletableFuture future; if (output.output() instanceof RuntimeException e) { - future = protocol.serializeError(job, e); + protocol.serializeError(job, e).join(); } else { - future = protocol.serializeOutput(job, output.output()); + protocol.serializeOutput(job, output.output()).join(); } - return future.thenApply(ignored -> { - return HttpResponse.builder() - .statusCode(response.getStatusCode()) - .headers(response.headers()) - .body(response.getSerializedValue()) - .build(); - }); + return HttpResponse.builder() + .statusCode(response.getStatusCode()) + .headers(response.headers()) + .body(response.getSerializedValue()) + .build(); } private static ServerProtocol detectServerProtocol(ShapeId id) { diff --git a/client/client-rpcv2-cbor/src/main/java/software/amazon/smithy/java/client/rpcv2/RpcV2CborProtocol.java b/client/client-rpcv2-cbor/src/main/java/software/amazon/smithy/java/client/rpcv2/RpcV2CborProtocol.java index d2667e573..12c17f1c9 100644 --- a/client/client-rpcv2-cbor/src/main/java/software/amazon/smithy/java/client/rpcv2/RpcV2CborProtocol.java +++ b/client/client-rpcv2-cbor/src/main/java/software/amazon/smithy/java/client/rpcv2/RpcV2CborProtocol.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.aws.events.AwsEventDecoderFactory; import software.amazon.smithy.java.aws.events.AwsEventEncoderFactory; import software.amazon.smithy.java.aws.events.AwsEventFrame; @@ -90,7 +89,7 @@ public HttpRequest } @Override - public CompletableFuture deserializeResponse( + public O deserializeResponse( ApiOperation operation, Context context, TypeRegistry typeRegistry, @@ -98,10 +97,7 @@ public CompletableF HttpResponse response ) { if (response.statusCode() != 200) { - return errorDeserializer.createError(context, operation.schema().id(), typeRegistry, response) - .thenApply(e -> { - throw e; - }); + throw errorDeserializer.createError(context, operation.schema().id(), typeRegistry, response); } if (operation instanceof OutputEventStreamingApiOperation o) { @@ -112,12 +108,11 @@ public CompletableF var builder = operation.outputBuilder(); var content = response.body(); if (content.contentLength() == 0) { - return CompletableFuture.completedFuture(builder.build()); + return builder.build(); } - return content.asByteBuffer() - .thenApply(bytes -> CBOR_CODEC.deserializeShape(bytes, builder)) - .toCompletableFuture(); + var bytes = content.waitForByteBuffer(); + return CBOR_CODEC.deserializeShape(bytes, builder); } private static DataStream bodyDataStream(HttpResponse response) { @@ -165,9 +160,7 @@ private EventEncoderFactory getEventEncoderFactory( private EventDecoderFactory getEventDecoderFactory( OutputEventStreamingApiOperation outputOperation ) { - return AwsEventDecoderFactory.forOutputStream(outputOperation, - payloadCodec(), - f -> f); + return AwsEventDecoderFactory.forOutputStream(outputOperation, payloadCodec(), f -> f); } public static final class Factory implements ClientProtocolFactory { diff --git a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeCompiler.java b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeCompiler.java index cbc4106a5..36bd4e52a 100644 --- a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeCompiler.java +++ b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeCompiler.java @@ -90,17 +90,17 @@ Bytecode compile() { // Compile all results for (Rule result : bdd.getResults()) { writer.markResultStart(); - if (result == null || result instanceof NoMatchRule) { - // No match: push null and return - writer.writeByte(Opcodes.LOAD_CONST); - writer.writeByte(writer.getConstantIndex(null)); - writer.writeByte(Opcodes.RETURN_VALUE); - } else if (result instanceof EndpointRule e) { - compileEndpointRule(e); - } else if (result instanceof ErrorRule e) { - compileErrorRule(e); - } else { - throw new UnsupportedOperationException("Unexpected result type: " + result.getClass()); + result = result == null ? NoMatchRule.INSTANCE : result; + switch (result) { + case NoMatchRule nr -> { + // No match: push null and return + writer.writeByte(Opcodes.LOAD_CONST); + writer.writeByte(writer.getConstantIndex(null)); + writer.writeByte(Opcodes.RETURN_VALUE); + } + case EndpointRule e -> compileEndpointRule(e); + case ErrorRule e -> compileErrorRule(e); + default -> throw new UnsupportedOperationException("Unexpected result type: " + result); } } @@ -375,47 +375,49 @@ private void compileGetAttr(GetAttr getAttr) { } private void compileLiteral(Literal literal) { - if (literal instanceof StringLiteral s) { - var template = s.value(); - var parts = template.getParts(); - - if (parts.size() == 1 && parts.get(0) instanceof Template.Literal) { - // Simple string with no interpolation - addLoadConst(parts.get(0).toString()); - } else if (parts.size() == 1 && parts.get(0) instanceof Template.Dynamic dynamic) { - // Single dynamic expression, so just evaluate it - compileExpression(dynamic.toExpression()); - } else { - // Multiple parts - need to concatenate - int expressionCount = 0; - for (var part : parts) { - if (part instanceof Template.Dynamic d) { - compileExpression(d.toExpression()); - } else { - addLoadConst(part.toString()); + switch (literal) { + case StringLiteral s -> { + var template = s.value(); + var parts = template.getParts(); + + if (parts.size() == 1 && parts.get(0) instanceof Template.Literal) { + // Simple string with no interpolation + addLoadConst(parts.get(0).toString()); + } else if (parts.size() == 1 && parts.get(0) instanceof Template.Dynamic dynamic) { + // Single dynamic expression, so just evaluate it + compileExpression(dynamic.toExpression()); + } else { + // Multiple parts - need to concatenate + int expressionCount = 0; + for (var part : parts) { + if (part instanceof Template.Dynamic d) { + compileExpression(d.toExpression()); + } else { + addLoadConst(part.toString()); + } + expressionCount++; } - expressionCount++; + writer.writeByte(Opcodes.RESOLVE_TEMPLATE); + writer.writeByte(expressionCount); } - writer.writeByte(Opcodes.RESOLVE_TEMPLATE); - writer.writeByte(expressionCount); } - } else if (literal instanceof TupleLiteral t) { - for (var e : t.members()) { - compileLiteral(e); + case TupleLiteral t -> { + for (var e : t.members()) { + compileLiteral(e); + } + compileListCreation(t.members().size()); } - compileListCreation(t.members().size()); - } else if (literal instanceof RecordLiteral r) { - for (var e : r.members().entrySet()) { - compileLiteral(e.getValue()); // value then key to make popping ordered - addLoadConst(e.getKey().toString()); + case RecordLiteral r -> { + for (var e : r.members().entrySet()) { + compileLiteral(e.getValue()); // value then key to make popping ordered + addLoadConst(e.getKey().toString()); + } + compileMapCreation(r.members().size()); } - compileMapCreation(r.members().size()); - } else if (literal instanceof BooleanLiteral b) { - addLoadConst(b.value().getValue()); - } else if (literal instanceof IntegerLiteral i) { - addLoadConst(i.toNode().expectNumberNode().getValue()); - } else { - throw new UnsupportedOperationException("Unexpected rules engine Literal type: " + literal); + case BooleanLiteral b -> addLoadConst(b.value().getValue()); + case IntegerLiteral i -> addLoadConst(i.toNode().expectNumberNode().getValue()); + case null, default -> + throw new UnsupportedOperationException("Unexpected rules engine Literal type: " + literal); } } diff --git a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeDisassembler.java b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeDisassembler.java index 6270b6e85..325127d87 100644 --- a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeDisassembler.java +++ b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeDisassembler.java @@ -338,17 +338,13 @@ private void appendSymbolicInfo(StringBuilder s, BytecodeWalker walker, Show sho } private String formatConstant(Object value) { - if (value == null) { - return "null"; - } else if (value instanceof String) { - return "\"" + escapeString((String) value) + "\""; - } else if (value instanceof List list) { - return "List[" + list.size() + " items]"; - } else if (value instanceof Map map) { - return "Map[" + map.size() + " entries]"; - } else { - return value.getClass().getSimpleName() + "[" + value + "]"; - } + return switch (value) { + case null -> "null"; + case String s -> "\"" + escapeString(s) + "\""; + case List list -> "List[" + list.size() + " items]"; + case Map map -> "Map[" + map.size() + " entries]"; + default -> value.getClass().getSimpleName() + "[" + value + "]"; + }; } private String formatValue(Object value) { diff --git a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolver.java b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolver.java index 0a1353ce1..e70ec63bb 100644 --- a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolver.java +++ b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolver.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; import software.amazon.smithy.java.client.core.endpoint.Endpoint; import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; @@ -22,7 +21,6 @@ public final class BytecodeEndpointResolver implements EndpointResolver { private static final InternalLogger LOGGER = InternalLogger.getLogger(BytecodeEndpointResolver.class); - private static final CompletableFuture NULL_RESULT = CompletableFuture.completedFuture(null); private final Bytecode bytecode; private final Bdd bdd; @@ -48,31 +46,27 @@ public BytecodeEndpointResolver( } @Override - public CompletableFuture resolveEndpoint(EndpointResolverParams params) { - try { - var evaluator = threadLocalEvaluator.get(); - var operation = params.operation(); - var ctx = params.context(); + public Endpoint resolveEndpoint(EndpointResolverParams params) { + var evaluator = threadLocalEvaluator.get(); + var operation = params.operation(); + var ctx = params.context(); - // Get reusable params array and clear it - var inputParams = evaluator.paramsCache; - inputParams.clear(); + // Get reusable params array and clear it + var inputParams = evaluator.paramsCache; + inputParams.clear(); - // Prep the input parameters by grabbing them from the input and from other traits. - ContextProvider.createEndpointParams(inputParams, ctxProvider, ctx, operation, params.inputValue()); + // Prep the input parameters by grabbing them from the input and from other traits. + ContextProvider.createEndpointParams(inputParams, ctxProvider, ctx, operation, params.inputValue()); - // Reset the evaluator and prepare new registers. - evaluator.reset(ctx, inputParams); + // Reset the evaluator and prepare new registers. + evaluator.reset(ctx, inputParams); - LOGGER.debug("Resolving endpoint of {} using VM with params: {}", operation, inputParams); + LOGGER.debug("Resolving endpoint of {} using VM with params: {}", operation, inputParams); - var resultIndex = bdd.evaluate(evaluator); - if (resultIndex < 0) { - return NULL_RESULT; - } - return CompletableFuture.completedFuture(evaluator.resolveResult(resultIndex)); - } catch (RulesEvaluationError e) { - return CompletableFuture.failedFuture(e); + var resultIndex = bdd.evaluate(evaluator); + if (resultIndex < 0) { + return null; } + return evaluator.resolveResult(resultIndex); } } diff --git a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEvaluator.java b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEvaluator.java index b728edcec..a74328faf 100644 --- a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEvaluator.java +++ b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeEvaluator.java @@ -400,10 +400,9 @@ private Object run(int start) { // Get a property from a map or URI, or return null. private Object getProperty(Object target, String propertyName) { - if (target instanceof Map m) { - return m.get(propertyName); - } else if (target instanceof URI u) { - return switch (propertyName) { + return switch (target) { + case Map m -> m.get(propertyName); + case URI u -> switch (propertyName) { case "scheme" -> u.getScheme(); case "path" -> u.getRawPath(); case "normalizedPath" -> ParseUrl.normalizePath(u.getRawPath()); @@ -411,8 +410,8 @@ private Object getProperty(Object target, String propertyName) { case "isIp" -> ParseUrl.isIpAddr(u.getHost()); default -> null; }; - } - return null; + case null, default -> null; + }; } // Get a value by index from an object. If not an array, returns null. diff --git a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeWriter.java b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeWriter.java index 41baeb89e..a6c27bc7c 100644 --- a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeWriter.java +++ b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/BytecodeWriter.java @@ -297,35 +297,39 @@ private void writeConstantPool(DataOutputStream dos) throws IOException { } private void writeConstantValue(DataOutputStream dos, Object value) throws IOException { - if (value == null) { - dos.writeByte(Bytecode.CONST_NULL); - } else if (value instanceof String s) { - dos.writeByte(Bytecode.CONST_STRING); - writeUTF(dos, s); - } else if (value instanceof Integer i) { - dos.writeByte(Bytecode.CONST_INTEGER); - dos.writeInt(i); - } else if (value instanceof Boolean b) { - dos.writeByte(Bytecode.CONST_BOOLEAN); - dos.writeByte(b ? 1 : 0); - } else if (value instanceof List list) { - dos.writeByte(Bytecode.CONST_LIST); - dos.writeShort(list.size()); - for (Object element : list) { - writeConstantValue(dos, element); + switch (value) { + case null -> dos.writeByte(Bytecode.CONST_NULL); + case String s -> { + dos.writeByte(Bytecode.CONST_STRING); + writeUTF(dos, s); } - } else if (value instanceof Map map) { - dos.writeByte(Bytecode.CONST_MAP); - dos.writeShort(map.size()); - for (Map.Entry entry : map.entrySet()) { - if (!(entry.getKey() instanceof String)) { - throw new IOException("Map keys must be strings, found: " + entry.getKey().getClass()); + case Integer i -> { + dos.writeByte(Bytecode.CONST_INTEGER); + dos.writeInt(i); + } + case Boolean b -> { + dos.writeByte(Bytecode.CONST_BOOLEAN); + dos.writeByte(b ? 1 : 0); + } + case List list -> { + dos.writeByte(Bytecode.CONST_LIST); + dos.writeShort(list.size()); + for (Object element : list) { + writeConstantValue(dos, element); } - writeUTF(dos, (String) entry.getKey()); - writeConstantValue(dos, entry.getValue()); } - } else { - throw new IOException("Unsupported constant type: " + value.getClass()); + case Map map -> { + dos.writeByte(Bytecode.CONST_MAP); + dos.writeShort(map.size()); + for (Map.Entry entry : map.entrySet()) { + if (!(entry.getKey() instanceof String)) { + throw new IOException("Map keys must be strings, found: " + entry.getKey().getClass()); + } + writeUTF(dos, (String) entry.getKey()); + writeConstantValue(dos, entry.getValue()); + } + } + default -> throw new IOException("Unsupported constant type: " + value.getClass()); } } diff --git a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolver.java b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolver.java index 00d8b3da2..ded7b6672 100644 --- a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolver.java +++ b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolver.java @@ -8,7 +8,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; import software.amazon.smithy.java.client.core.endpoint.Endpoint; import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; @@ -78,56 +77,52 @@ final class DecisionTreeEndpointResolver implements EndpointResolver { } @Override - public CompletableFuture resolveEndpoint(EndpointResolverParams params) { - try { - var operation = params.operation(); - - // Start with defaults - Map input = new HashMap<>(defaultValues); - - // Collect and apply supplied parameters (typically just a few) - Map endpointParams = new HashMap<>(); - ContextProvider.createEndpointParams( - endpointParams, - operationContextParams, - params.context(), - operation, - params.inputValue()); - - // Convert supplied values and override defaults - for (var e : endpointParams.entrySet()) { - Identifier id = paramNameToIdentifier.get(e.getKey()); - if (id != null) { - input.put(id, EndpointUtils.convertToValue(e.getValue())); - } + public Endpoint resolveEndpoint(EndpointResolverParams params) { + var operation = params.operation(); + + // Start with defaults + Map input = new HashMap<>(defaultValues); + + // Collect and apply supplied parameters (typically just a few) + Map endpointParams = new HashMap<>(); + ContextProvider.createEndpointParams( + endpointParams, + operationContextParams, + params.context(), + operation, + params.inputValue()); + + // Convert supplied values and override defaults + for (var e : endpointParams.entrySet()) { + Identifier id = paramNameToIdentifier.get(e.getKey()); + if (id != null) { + input.put(id, EndpointUtils.convertToValue(e.getValue())); } + } - // Apply builtins only for parameters that need them - var context = params.context(); - for (var entry : builtinById.entrySet()) { - Identifier id = entry.getKey(); - if (!input.containsKey(id)) { - Object value = entry.getValue().apply(context); - if (value != null) { - input.put(id, EndpointUtils.convertToValue(value)); - } + // Apply builtins only for parameters that need them + var context = params.context(); + for (var entry : builtinById.entrySet()) { + Identifier id = entry.getKey(); + if (!input.containsKey(id)) { + Object value = entry.getValue().apply(context); + if (value != null) { + input.put(id, EndpointUtils.convertToValue(value)); } } + } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Resolving endpoint of {} using VM with params: {}", operation, input); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Resolving endpoint of {} using VM with params: {}", operation, input); + } - var result = RuleEvaluator.evaluate(rules, input); - if (result instanceof EndpointValue ev) { - return CompletableFuture.completedFuture(convertEndpoint(params, ev)); - } else if (result instanceof StringValue sv) { - return CompletableFuture.failedFuture(new RulesEvaluationError(sv.getValue())); - } else { - throw new IllegalStateException("Expected decision tree to return an endpoint, but found " + result); - } - } catch (RulesEvaluationError e) { - return CompletableFuture.failedFuture(e); + var result = RuleEvaluator.evaluate(rules, input); + if (result instanceof EndpointValue ev) { + return convertEndpoint(params, ev); + } else if (result instanceof StringValue sv) { + throw new RulesEvaluationError(sv.getValue()); + } else { + throw new IllegalStateException("Expected decision tree to return an endpoint, but found " + result); } } diff --git a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/EndpointUtils.java b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/EndpointUtils.java index 1bb39147f..2cb3f7bac 100644 --- a/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/EndpointUtils.java +++ b/client/client-rulesengine/src/main/java/software/amazon/smithy/java/client/rulesengine/EndpointUtils.java @@ -56,29 +56,27 @@ public static Object convertNode(Node value) { } static Value convertToValue(Object o) { - if (o == null) { - return Value.emptyValue(); - } else if (o instanceof String s) { - return Value.stringValue(s); - } else if (o instanceof Number n) { - return Value.integerValue(n.intValue()); - } else if (o instanceof Boolean b) { - return Value.booleanValue(b); - } else if (o instanceof List l) { - List valueList = new ArrayList<>(l.size()); - for (var entry : l) { - valueList.add(convertToValue(entry)); + return switch (o) { + case null -> Value.emptyValue(); + case String s -> Value.stringValue(s); + case Number n -> Value.integerValue(n.intValue()); + case Boolean b -> Value.booleanValue(b); + case List l -> { + List valueList = new ArrayList<>(l.size()); + for (var entry : l) { + valueList.add(convertToValue(entry)); + } + yield Value.arrayValue(valueList); } - return Value.arrayValue(valueList); - } else if (o instanceof Map m) { - Map valueMap = new HashMap<>(m.size()); - for (var e : m.entrySet()) { - valueMap.put(Identifier.of(e.getKey().toString()), convertToValue(e.getValue())); + case Map m -> { + Map valueMap = new HashMap<>(m.size()); + for (var e : m.entrySet()) { + valueMap.put(Identifier.of(e.getKey().toString()), convertToValue(e.getValue())); + } + yield Value.recordValue(valueMap); } - return Value.recordValue(valueMap); - } else { - throw new RulesEvaluationError("Unsupported value type: " + o); - } + default -> throw new RulesEvaluationError("Unsupported value type: " + o); + }; } // Read big-endian unsigned short (2 bytes) diff --git a/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolverTest.java b/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolverTest.java index fab4b0443..312e4bc4f 100644 --- a/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolverTest.java +++ b/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/BytecodeEndpointResolverTest.java @@ -6,17 +6,15 @@ package software.amazon.smithy.java.client.rulesengine; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.client.core.ClientContext; import software.amazon.smithy.java.client.core.endpoint.Endpoint; @@ -34,7 +32,7 @@ class BytecodeEndpointResolverTest { @Test - void testSimpleEndpointResolution() throws Exception { + void testSimpleEndpointResolution() { // Create a simple bytecode that returns a fixed endpoint // BDD: root -> result 0 (always returns result 0) Bytecode bytecode = new Bytecode( @@ -63,15 +61,14 @@ void testSimpleEndpointResolution() throws Exception { EndpointResolverParams params = createParams("us-east-1", "my-bucket"); - CompletableFuture future = resolver.resolveEndpoint(params); - Endpoint endpoint = future.get(); + Endpoint endpoint = resolver.resolveEndpoint(params); assertNotNull(endpoint); assertEquals("https://example.com", endpoint.uri().toString()); } @Test - void testEndpointWithBuiltinProvider() throws Exception { + void testEndpointWithBuiltinProvider() { // Create bytecode that uses a builtin // The BDD just returns result 0 which uses the builtin value Bytecode bytecode = new Bytecode( @@ -113,12 +110,12 @@ void testEndpointWithBuiltinProvider() throws Exception { EndpointResolverParams params = createParams("us-west-2", "bucket", context); - Endpoint endpoint = resolver.resolveEndpoint(params).get(); + Endpoint endpoint = resolver.resolveEndpoint(params); assertEquals("https://custom.example.com", endpoint.uri().toString()); } @Test - void testNoMatchReturnsNull() throws Exception { + void testNoMatchReturnsNull() { // Create bytecode that returns no match (result 0 is NoMatchRule) Bytecode bytecode = new Bytecode( new byte[] { @@ -142,12 +139,12 @@ void testNoMatchReturnsNull() throws Exception { EndpointResolverParams params = createParams("us-east-1", "bucket"); - Endpoint endpoint = resolver.resolveEndpoint(params).get(); + Endpoint endpoint = resolver.resolveEndpoint(params); assertNull(endpoint); } @Test - void testConditionalEndpoint() throws Exception { + void testConditionalEndpoint() { // Create bytecode with a condition that checks if region is set // Condition 0: isSet(region) // Result 0: no match (returns null) @@ -219,50 +216,18 @@ void testConditionalEndpoint() throws Exception { // Test with region provided EndpointResolverParams params = createParams("us-east-1", "bucket"); - Endpoint endpoint = resolver.resolveEndpoint(params).get(); + Endpoint endpoint = resolver.resolveEndpoint(params); assertNotNull(endpoint); assertEquals("https://example.com", endpoint.uri().toString()); // Test without region params = createParams(null, "bucket"); - endpoint = resolver.resolveEndpoint(params).get(); + endpoint = resolver.resolveEndpoint(params); assertNull(endpoint); // Should return no match (result 0) } @Test - void testErrorPropagation() { - // Create bytecode that throws an error - Bytecode bytecode = new Bytecode( - new byte[] { - Opcodes.LOAD_CONST, - 0, - Opcodes.RETURN_ERROR - }, - new int[0], // No conditions - new int[] {0}, // One result at offset 0 - new RegisterDefinition[0], - new Object[] {"Test error"}, - new RulesFunction[0], - new int[] {-1, 100_000_000, -1}, // Terminal node that returns result 0 - 100_000_000 // Root points to result 0 - ); - - BytecodeEndpointResolver resolver = new BytecodeEndpointResolver( - bytecode, - List.of(), - Map.of()); - - EndpointResolverParams params = createParams("us-east-1", "bucket"); - - CompletableFuture future = resolver.resolveEndpoint(params); - - Exception exception = assertThrows(Exception.class, future::get); - assertInstanceOf(RulesEvaluationError.class, exception.getCause()); - assertTrue(exception.getCause().getMessage().contains("Test error")); - } - - @Test - void testWithRulesExtension() throws Exception { + void testWithRulesExtension() { Bytecode bytecode = new Bytecode( new byte[] { Opcodes.LOAD_CONST, @@ -289,7 +254,7 @@ void testWithRulesExtension() throws Exception { EndpointResolverParams params = createParams("us-east-1", "bucket"); - Endpoint endpoint = resolver.resolveEndpoint(params).get(); + Endpoint endpoint = resolver.resolveEndpoint(params); assertTrue(extension.wasCalled); assertNotNull(endpoint); @@ -297,7 +262,6 @@ void testWithRulesExtension() throws Exception { @Test void testMissingRequiredParameter() { - // Create bytecode with required parameter RegisterDefinition[] defs = { new RegisterDefinition("region", true, null, null, false), new RegisterDefinition("bucket", true, null, null, false) @@ -321,15 +285,12 @@ void testMissingRequiredParameter() { // Only provide region, not bucket EndpointResolverParams params = createParams("us-east-1", null); - CompletableFuture future = resolver.resolveEndpoint(params); - - Exception exception = assertThrows(Exception.class, future::get); - assertInstanceOf(RulesEvaluationError.class, exception.getCause()); - assertTrue(exception.getCause().getMessage().contains("bucket")); + var e = Assertions.assertThrows(RulesEvaluationError.class, () -> resolver.resolveEndpoint(params)); + assertTrue(e.getMessage().contains("bucket")); } @Test - void testParameterWithDefaultValue() throws Exception { + void testParameterWithDefaultValue() { // Create bytecode with a parameter that has a default value RegisterDefinition[] defs = { new RegisterDefinition("region", true, "us-west-2", null, false), // Has default @@ -365,7 +326,7 @@ void testParameterWithDefaultValue() throws Exception { // Don't provide region, should use default EndpointResolverParams params = createParams(null, "my-bucket"); - Endpoint endpoint = resolver.resolveEndpoint(params).get(); + Endpoint endpoint = resolver.resolveEndpoint(params); assertNotNull(endpoint); assertEquals("us-west-2/my-bucket", endpoint.uri().toString()); } diff --git a/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolverTest.java b/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolverTest.java index 17b9f783f..f0e47aa66 100644 --- a/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolverTest.java +++ b/client/client-rulesengine/src/test/java/software/amazon/smithy/java/client/rulesengine/DecisionTreeEndpointResolverTest.java @@ -6,9 +6,7 @@ package software.amazon.smithy.java.client.rulesengine; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; @@ -16,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.client.core.endpoint.EndpointResolverParams; @@ -61,7 +60,7 @@ void setUp() { } @Test - void testSimpleEndpointResolution() throws Exception { + void testSimpleEndpointResolution() { var url = "https://example.com"; var endpointRule = EndpointRule.builder().endpoint(Endpoint.builder().url(Expression.of(url)).build()); @@ -72,15 +71,14 @@ void testSimpleEndpointResolution() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertNotNull(endpoint); assertEquals(URI.create(url), endpoint.uri()); } @Test - void testParameterWithDefault() throws Exception { + void testParameterWithDefault() { Parameter param = Parameter.builder() .name(Identifier.of("region")) .type(ParameterType.STRING) @@ -98,14 +96,13 @@ void testParameterWithDefault() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertEquals(URI.create("us-east-1"), endpoint.uri()); } @Test - void testParameterOverridesDefault() throws Exception { + void testParameterOverridesDefault() { Parameter param = Parameter.builder() .name(Identifier.of("region")) .type(ParameterType.STRING) @@ -127,14 +124,13 @@ void testParameterOverridesDefault() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertEquals(URI.create("us-west-2"), endpoint.uri()); } @Test - void testBuiltinParameter() throws Exception { + void testBuiltinParameter() { Parameter param = Parameter.builder() .name(Identifier.of("endpoint")) .type(ParameterType.STRING) @@ -159,14 +155,13 @@ void testBuiltinParameter() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertEquals(URI.create("https://custom.example.com"), endpoint.uri()); } @Test - void testBuiltinReturnsNull() throws Exception { + void testBuiltinReturnsNull() { Parameter param = Parameter.builder() .name(Identifier.of("endpoint")) .type(ParameterType.STRING) @@ -187,14 +182,13 @@ void testBuiltinReturnsNull() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertEquals(URI.create("https://default.com"), endpoint.uri()); } @Test - void testMultipleParameterTypes() throws Exception { + void testMultipleParameterTypes() { var stringParam = Parameter.builder() .name(Identifier.of("bucket")) .type(ParameterType.STRING) @@ -233,8 +227,7 @@ void testMultipleParameterTypes() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertNotNull(endpoint); assertEquals(URI.create("https://example.com"), endpoint.uri()); @@ -262,15 +255,13 @@ void testErrorRuleEvaluation() { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - Exception exception = assertThrows(Exception.class, future::get); - assertInstanceOf(RulesEvaluationError.class, exception.getCause()); - assertTrue(exception.getCause().getMessage().contains("Invalid configuration")); + var e = Assertions.assertThrows(RulesEvaluationError.class, () -> resolver.resolveEndpoint(params)); + assertTrue(e.getMessage().contains("Invalid configuration")); } @Test - void testParameterPriority() throws Exception { + void testParameterPriority() { Parameter param = Parameter.builder() .name(Identifier.of("region")) .type(ParameterType.STRING) @@ -295,14 +286,13 @@ void testParameterPriority() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertEquals(URI.create("supplied-region"), endpoint.uri()); } @Test - void testListParameterConversion() throws Exception { + void testListParameterConversion() { Parameter param = Parameter.builder() .name(Identifier.of("regions")) .type(ParameterType.STRING_ARRAY) @@ -322,14 +312,13 @@ void testListParameterConversion() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertNotNull(endpoint); } @Test - void testMapParameterConversion() throws Exception { + void testMapParameterConversion() { var endpointRule = EndpointRule.builder() .endpoint(Endpoint.builder().url(Expression.of(("https://example.com"))).build()); @@ -347,8 +336,7 @@ void testMapParameterConversion() throws Exception { var resolver = new DecisionTreeEndpointResolver(ruleSet, List.of(), builtinProviders); var params = createParams(operation, input, context); - var future = resolver.resolveEndpoint(params); - var endpoint = future.get(); + var endpoint = resolver.resolveEndpoint(params); assertNotNull(endpoint); } diff --git a/client/client-waiters/src/test/java/software/amazon/smithy/java/client/waiters/MockClient.java b/client/client-waiters/src/test/java/software/amazon/smithy/java/client/waiters/MockClient.java index 9b867ac48..10adeea73 100644 --- a/client/client-waiters/src/test/java/software/amazon/smithy/java/client/waiters/MockClient.java +++ b/client/client-waiters/src/test/java/software/amazon/smithy/java/client/waiters/MockClient.java @@ -8,9 +8,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import software.amazon.smithy.java.client.core.RequestOverrideConfig; import software.amazon.smithy.java.client.waiters.models.GetFoosInput; import software.amazon.smithy.java.client.waiters.models.GetFoosOutput; @@ -18,7 +15,6 @@ import software.amazon.smithy.java.core.schema.SerializableStruct; final class MockClient { - private final Executor executor = CompletableFuture.delayedExecutor(3, TimeUnit.MILLISECONDS); private final String expectedId; private final Iterator iterator; @@ -28,10 +24,6 @@ final class MockClient { } public GetFoosOutput getFoosSync(GetFoosInput in, RequestOverrideConfig override) { - return getFoosAsync(in, override).join(); - } - - public CompletableFuture getFoosAsync(GetFoosInput in, RequestOverrideConfig override) { if (!Objects.equals(expectedId, in.id())) { throw new IllegalArgumentException("ID: " + in.id() + " does not match expected " + expectedId); } else if (!iterator.hasNext()) { @@ -39,7 +31,7 @@ public CompletableFuture getFoosAsync(GetFoosInput in, RequestOve } var next = iterator.next(); if (next instanceof GetFoosOutput output) { - return CompletableFuture.supplyAsync(() -> output, executor); + return output; } else if (next instanceof ModeledException exc) { // Throw exception throw exc; diff --git a/client/dynamic-client/src/main/java/software/amazon/smithy/java/dynamicclient/DynamicClient.java b/client/dynamic-client/src/main/java/software/amazon/smithy/java/dynamicclient/DynamicClient.java index 8bce5b41f..be0af1535 100644 --- a/client/dynamic-client/src/main/java/software/amazon/smithy/java/dynamicclient/DynamicClient.java +++ b/client/dynamic-client/src/main/java/software/amazon/smithy/java/dynamicclient/DynamicClient.java @@ -13,11 +13,8 @@ import java.util.Map; import java.util.Objects; import java.util.ServiceLoader; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.function.Function; import software.amazon.smithy.java.client.core.Client; import software.amazon.smithy.java.client.core.ClientProtocolFactory; import software.amazon.smithy.java.client.core.ProtocolSettings; @@ -83,7 +80,7 @@ private DynamicClient(Builder builder, SchemaConverter converter, ServiceShape s // Build and register service-wide errors. var registryBuilder = TypeRegistry.builder(); - for (var e : service.getErrors()) { + for (var e : service.getErrorsSet()) { registerError(e, registryBuilder); } this.serviceErrorRegistry = registryBuilder.build(); @@ -136,50 +133,9 @@ public Document call(String operation, Document input) { * @return the output of the operation. */ public Document call(String operation, Document input, RequestOverrideConfig overrideConfig) { - try { - return callAsync(operation, input, overrideConfig).join(); - } catch (CompletionException e) { - throw unwrapAndThrow(e); - } - } - - /** - * Call an operation, passing no input. - * - * @param operation Operation name to call. - * @return the output of the operation. - */ - public CompletableFuture callAsync(String operation) { - return callAsync(operation, Document.of(Map.of())); - } - - /** - * Call an operation with input. - * - * @param operation Operation name to call. - * @param input Operation input as a document. - * @return the output of the operation. - */ - public CompletableFuture callAsync(String operation, Document input) { - return callAsync(operation, input, null); - } - - /** - * Call an operation with input and custom request override configuration. - * - * @param operation Operation name to call. - * @param input Operation input as a document. - * @param overrideConfig Override configuration for the request. - * @return the output of the operation. - */ - public CompletableFuture callAsync( - String operation, - Document input, - RequestOverrideConfig overrideConfig - ) { var apiOperation = getApiOperation(operation); var inputStruct = StructDocument.of(apiOperation.inputSchema(), input, service.getId()); - return call(inputStruct, apiOperation, overrideConfig).thenApply(Function.identity()); + return call(inputStruct, apiOperation, overrideConfig); } /** @@ -230,9 +186,9 @@ private ApiOperation getApiOperation(String name var errorSchemas = new HashSet(); // Create a type registry that is able to deserialize errors using schemas. - if (!shape.getErrors().isEmpty()) { + if (!shape.getErrorsSet().isEmpty()) { var registryBuilder = TypeRegistry.builder(); - for (var e : shape.getErrors()) { + for (var e : shape.getErrorsSet()) { registerError(e, registryBuilder); errorSchemas.add(schemaConverter.getSchema(model.expectShape(e))); } diff --git a/client/dynamic-client/src/test/java/software/amazon/smithy/java/dynamicclient/DynamicClientTest.java b/client/dynamic-client/src/test/java/software/amazon/smithy/java/dynamicclient/DynamicClientTest.java index 68db515e3..d2a860b23 100644 --- a/client/dynamic-client/src/test/java/software/amazon/smithy/java/dynamicclient/DynamicClientTest.java +++ b/client/dynamic-client/src/test/java/software/amazon/smithy/java/dynamicclient/DynamicClientTest.java @@ -11,7 +11,6 @@ import static org.hamcrest.Matchers.is; import java.util.Map; -import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -132,13 +131,12 @@ public MessageExchange messageExchange() { } @Override - public CompletableFuture send(Context context, HttpRequest request) { - return CompletableFuture.completedFuture( - HttpResponse.builder() - .httpVersion(HttpVersion.HTTP_1_1) - .statusCode(200) - .body(DataStream.ofString("{\"id\":\"1\"}")) - .build()); + public HttpResponse send(Context context, HttpRequest request) { + return HttpResponse.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .statusCode(200) + .body(DataStream.ofString("{\"id\":\"1\"}")) + .build(); } }; } @@ -162,7 +160,7 @@ public void readBeforeTransmit(RequestHook hook) { }) .build(); - var result = client.callAsync("GetSprocket", Document.ofObject(Map.of("id", "1"))).get(); + var result = client.call("GetSprocket", Document.ofObject(Map.of("id", "1"))); assertThat(result.type(), is(ShapeType.STRUCTURE)); assertThat(result.getMember("id").asString(), equalTo("1")); } @@ -237,13 +235,12 @@ public MessageExchange messageExchange() { } @Override - public CompletableFuture send(Context context, HttpRequest request) { - return CompletableFuture.completedFuture( - HttpResponse.builder() - .httpVersion(HttpVersion.HTTP_1_1) - .statusCode(400) - .body(DataStream.ofString(payload)) - .build()); + public HttpResponse send(Context context, HttpRequest request) { + return HttpResponse.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .statusCode(400) + .body(DataStream.ofString(payload)) + .build(); } }; } diff --git a/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/ClientErrorCorrectionTest.java b/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/ClientErrorCorrectionTest.java index ac7e49275..2c011f8fc 100644 --- a/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/ClientErrorCorrectionTest.java +++ b/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/ClientErrorCorrectionTest.java @@ -39,7 +39,7 @@ void correctsErrors() { assertEquals(corrected.getShort(), (short) 0); assertEquals(corrected.getBlob(), ByteBuffer.allocate(0)); assertEquals(corrected.getStreamingBlob().contentLength(), 0); - corrected.getStreamingBlob().asByteBuffer().thenAccept(bytes -> assertEquals(0, bytes.remaining())); + assertEquals(0, corrected.getStreamingBlob().waitForByteBuffer().remaining()); assertNull(corrected.getDocument()); assertEquals(corrected.getList(), List.of()); assertEquals(corrected.getMap(), Map.of()); diff --git a/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/DefaultsTest.java b/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/DefaultsTest.java index 2a5de2a44..42c083167 100644 --- a/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/DefaultsTest.java +++ b/codegen/codegen-core/src/it/java/software/amazon/smithy/java/codegen/test/DefaultsTest.java @@ -37,9 +37,8 @@ void setsCorrectDefault() { assertEquals(defaults.getLong(), 1); assertEquals(defaults.getShort(), (short) 1); assertEquals(defaults.getBlob(), ByteBuffer.wrap(Base64.getDecoder().decode("YmxvYg=="))); - defaults.getStreamingBlob() - .asByteBuffer() - .thenAccept(b -> assertEquals(b, ByteBuffer.wrap(Base64.getDecoder().decode("c3RyZWFtaW5n")))); + assertEquals(defaults.getStreamingBlob().waitForByteBuffer(), + ByteBuffer.wrap(Base64.getDecoder().decode("c3RyZWFtaW5n"))); assertEquals(defaults.getBoolDoc(), Document.of(true)); assertEquals(defaults.getStringDoc(), Document.of("string")); assertEquals(defaults.getNumberDoc(), Document.of(1)); diff --git a/codegen/codegen-core/src/main/java/software/amazon/smithy/java/codegen/writer/JavaWriter.java b/codegen/codegen-core/src/main/java/software/amazon/smithy/java/codegen/writer/JavaWriter.java index bafb3629b..76780b4be 100644 --- a/codegen/codegen-core/src/main/java/software/amazon/smithy/java/codegen/writer/JavaWriter.java +++ b/codegen/codegen-core/src/main/java/software/amazon/smithy/java/codegen/writer/JavaWriter.java @@ -181,17 +181,14 @@ public String apply(Object type, String indent) { } private static Symbol getTypeSymbol(Object type, char formatChar) { - if (type instanceof Symbol s) { - return s; - } else if (type instanceof Class c) { - return CodegenUtils.fromClass(c); - } else if (type instanceof SymbolReference r) { - return r.getSymbol(); - } else { - throw new IllegalArgumentException( + return switch (type) { + case Symbol s -> s; + case Class c -> CodegenUtils.fromClass(c); + case SymbolReference r -> r.getSymbol(); + case null, default -> throw new IllegalArgumentException( "Invalid type provided for " + formatChar + ". Expected a Symbol or Class" + " but found: `" + type + "`."); - } + }; } /** diff --git a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientImplMethodInterceptor.java b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientImplMethodInterceptor.java index b19454bd8..34208edd2 100644 --- a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientImplMethodInterceptor.java +++ b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientImplMethodInterceptor.java @@ -20,13 +20,9 @@ public Class sectionType() { @Override public void prepend(JavaWriter writer, ClientImplAdditionalMethodsSection section) { - // TODO: Support Async waiters - if (section.async()) { - return; - } var clientSymbol = symbolProvider.toSymbol(section.client()); writer.pushState(); - writer.putContext("container", WaiterCodegenUtils.getWaiterSymbol(clientSymbol, settings, section.async())); + writer.putContext("container", WaiterCodegenUtils.getWaiterSymbol(clientSymbol, settings)); writer.write(""" @Override public ${container:T} waiter() { diff --git a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientInterfaceMethodInterceptor.java b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientInterfaceMethodInterceptor.java index 897beef4c..fab49185f 100644 --- a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientInterfaceMethodInterceptor.java +++ b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterClientInterfaceMethodInterceptor.java @@ -20,13 +20,9 @@ public Class sectionType() { @Override public void prepend(JavaWriter writer, ClientInterfaceAdditionalMethodsSection section) { - // TODO: Support Async waiters - if (section.async()) { - return; - } var clientSymbol = symbolProvider.toSymbol(section.client()); writer.pushState(); - writer.putContext("container", WaiterCodegenUtils.getWaiterSymbol(clientSymbol, settings, section.async())); + writer.putContext("container", WaiterCodegenUtils.getWaiterSymbol(clientSymbol, settings)); writer.write(""" /** * Create a new {@link CoffeeShopWaiter} instance that uses this client for polling. diff --git a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterCodegenUtils.java b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterCodegenUtils.java index b33669e03..4939610aa 100644 --- a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterCodegenUtils.java +++ b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterCodegenUtils.java @@ -16,12 +16,11 @@ final class WaiterCodegenUtils { * Determine the symbol to use for the Waiter container * @param clientSymbol Symbol for the client this waiter container is being created for. * @param settings Code generation settings - * @param async whether this container will contain asynchronous waiters. * @return Symbol representing waiter container class. */ - static Symbol getWaiterSymbol(Symbol clientSymbol, JavaCodegenSettings settings, boolean async) { + static Symbol getWaiterSymbol(Symbol clientSymbol, JavaCodegenSettings settings) { var baseClientName = clientSymbol.getName().substring(0, clientSymbol.getName().lastIndexOf("Client")); - var waiterName = async ? baseClientName + "AsyncWaiter" : baseClientName + "Waiter"; + var waiterName = baseClientName + "Waiter"; return Symbol.builder() .name(waiterName) .namespace(format("%s.client", settings.packageNamespace()), ".") diff --git a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterContainerGenerator.java b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterContainerGenerator.java index 7a365ede4..897c7a090 100644 --- a/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterContainerGenerator.java +++ b/codegen/integrations/waiters-codegen/src/main/java/software/amazon/smithy/java/codegen/client/waiters/WaiterContainerGenerator.java @@ -43,8 +43,8 @@ public void accept(CodeGenerationContext context) { if (serviceSymbol.getProperty(ClientSymbolProperties.CLIENT_IMPL).isEmpty()) { return; } - // TODO : Allow for async waiters. - var symbol = WaiterCodegenUtils.getWaiterSymbol(serviceSymbol, context.settings(), false); + + var symbol = WaiterCodegenUtils.getWaiterSymbol(serviceSymbol, context.settings()); context.settings().addSymbol(symbol); context.writerDelegator().useSymbolWriter(symbol, writer -> { var template = """ diff --git a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientJavaSymbolProvider.java b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientJavaSymbolProvider.java index 426014bb2..92555edee 100644 --- a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientJavaSymbolProvider.java +++ b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientJavaSymbolProvider.java @@ -27,8 +27,8 @@ public ClientJavaSymbolProvider(Model model, ServiceShape service, String packag @Override public Symbol serviceShape(ServiceShape serviceShape) { - return getSymbolFromName(false).toBuilder() - .putProperty(ClientSymbolProperties.ASYNC_SYMBOL, getSymbolFromName(true)) + return getSymbolFromName() + .toBuilder() .putProperty(SymbolProperties.SERVICE_EXCEPTION, CodegenUtils.getServiceExceptionSymbol(packageNamespace(), serviceName)) .putProperty(SymbolProperties.SERVICE_API_SERVICE, @@ -36,12 +36,11 @@ public Symbol serviceShape(ServiceShape serviceShape) { .build(); } - private Symbol getSymbolFromName(boolean async) { - var name = async ? serviceName + "AsyncClient" : serviceName + "Client"; + private Symbol getSymbolFromName() { + var name = serviceName + "Client"; var symbol = Symbol.builder() .name(name) .putProperty(SymbolProperties.IS_PRIMITIVE, false) - .putProperty(ClientSymbolProperties.ASYNC, async) .namespace(format("%s.client", packageNamespace()), ".") .definitionFile(format("./%s/client/%s.java", packageNamespace().replace(".", "/"), name)) .build(); diff --git a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientSymbolProperties.java b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientSymbolProperties.java index 67c43b84d..18875b1bd 100644 --- a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientSymbolProperties.java +++ b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/ClientSymbolProperties.java @@ -14,19 +14,6 @@ * @see software.amazon.smithy.java.codegen.SymbolProperties for other properties that may be added to symbols. */ public final class ClientSymbolProperties { - - /** - * Indicates if a symbol represents an async implementation. - */ - public static final Property ASYNC = Property.named("is-async"); - - /** - * Symbol representing the async implementation of Symbol. - * - *

This property is expected on all {@code Service} shape symbols. - */ - public static final Property ASYNC_SYMBOL = Property.named("async-symbol"); - /** * Symbol representing the implementation class for a client. * diff --git a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientImplementationGenerator.java b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientImplementationGenerator.java index 0191ab5e0..187c12d10 100644 --- a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientImplementationGenerator.java +++ b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientImplementationGenerator.java @@ -39,10 +39,7 @@ public final class ClientImplementationGenerator @Override public void accept(GenerateServiceDirective directive) { - // Write Synchronous implementation writeForSymbol(directive.symbol(), directive); - // Write Async implementation - writeForSymbol(directive.symbol().expectProperty(ClientSymbolProperties.ASYNC_SYMBOL), directive); } public static void writeForSymbol( @@ -88,8 +85,7 @@ final class ${impl:T} extends ${client:T} implements ${interface:T} {${?implicit writer, directive.shape(), directive.symbolProvider(), - directive.model(), - symbol.expectProperty(ClientSymbolProperties.ASYNC))); + directive.model())); writer.write(template); writer.popState(); }); @@ -99,8 +95,7 @@ private record OperationMethodGenerator( JavaWriter writer, ServiceShape service, SymbolProvider symbolProvider, - Model model, - boolean async) implements Runnable { + Model model) implements Runnable { @Override public void run() { @@ -108,15 +103,14 @@ public void run() { var template = """ @Override - public ${?async}${future:T}<${/async}${output:T}${?async}>${/async} ${name:L}(${input:T} input, ${overrideConfig:T} overrideConfig) { - ${^async}try { - ${/async}return call(input, ${operation:T}.instance(), overrideConfig)${^async}.join()${/async};${^async} + public ${output:T} ${name:L}(${input:T} input, ${overrideConfig:T} overrideConfig) { + try { + return call(input, ${operation:T}.instance(), overrideConfig); } catch (${completionException:T} e) { throw unwrapAndThrow(e); - }${/async} + } } """; - writer.putContext("async", async); writer.putContext("overrideConfig", RequestOverrideConfig.class); var opIndex = OperationIndex.of(model); for (var operation : TopDownIndex.of(model).getContainedOperations(service)) { @@ -128,7 +122,7 @@ public void run() { writer.write(template); writer.popState(); } - writer.injectSection(new ClientImplAdditionalMethodsSection(service, async)); + writer.injectSection(new ClientImplAdditionalMethodsSection(service)); writer.popState(); } } diff --git a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java index 6b8d34edb..c335e858c 100644 --- a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java +++ b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java @@ -27,7 +27,6 @@ import software.amazon.smithy.java.client.core.ProtocolSettings; import software.amazon.smithy.java.client.core.RequestOverrideConfig; import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeFactory; -import software.amazon.smithy.java.client.core.pagination.AsyncPaginator; import software.amazon.smithy.java.client.core.pagination.Paginator; import software.amazon.smithy.java.codegen.CodeGenerationContext; import software.amazon.smithy.java.codegen.CodegenUtils; @@ -82,10 +81,7 @@ public final class ClientInterfaceGenerator @Override public void accept(GenerateServiceDirective directive) { - // Write synchronous interface writeForSymbol(directive.symbol(), directive); - // Write async interface - writeForSymbol(directive.symbol().expectProperty(ClientSymbolProperties.ASYNC_SYMBOL), directive); } private static void writeForSymbol( @@ -312,13 +308,13 @@ private record OperationMethodGenerator( @Override public void run() { var templateDefault = """ - default ${?async}${future:T}<${/async}${output:T}${?async}>${/async} ${name:L}(${input:T} input) { + default ${output:T} ${name:L}(${input:T} input) { return ${name:L}(input, null); } """; var templateBase = """ - ${?async}${future:T}<${/async}${output:T}${?async}>${/async} ${name:L}(${input:T} input, ${overrideConfig:T} overrideConfig); + ${output:T} ${name:L}(${input:T} input, ${overrideConfig:T} overrideConfig); """; var templatePaginated = """ /** @@ -332,11 +328,9 @@ public void run() { } """; writer.pushState(); - var isAsync = symbol.expectProperty(ClientSymbolProperties.ASYNC); - writer.putContext("async", isAsync); writer.putContext("overrideConfig", RequestOverrideConfig.class); writer.putContext("future", CompletableFuture.class); - writer.putContext("paginator", isAsync ? AsyncPaginator.class : Paginator.class); + writer.putContext("paginator", Paginator.class); var opIndex = OperationIndex.of(model); for (var operation : TopDownIndex.of(model).getContainedOperations(service)) { @@ -361,7 +355,7 @@ public void run() { writer.popState(); } // Add any additional operations from integrations - writer.injectSection(new ClientInterfaceAdditionalMethodsSection(service, isAsync)); + writer.injectSection(new ClientInterfaceAdditionalMethodsSection(service)); writer.popState(); } } diff --git a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientImplAdditionalMethodsSection.java b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientImplAdditionalMethodsSection.java index 5a61e442e..0c1876470 100644 --- a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientImplAdditionalMethodsSection.java +++ b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientImplAdditionalMethodsSection.java @@ -12,6 +12,5 @@ * This section provides a hook for adding additional methods to the generated client implementation. * * @param client Client shape that these methods will be added to. - * @param async true if the client is an async client. */ -public record ClientImplAdditionalMethodsSection(ServiceShape client, boolean async) implements CodeSection {} +public record ClientImplAdditionalMethodsSection(ServiceShape client) implements CodeSection {} diff --git a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientInterfaceAdditionalMethodsSection.java b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientInterfaceAdditionalMethodsSection.java index a567ff611..f3e777780 100644 --- a/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientInterfaceAdditionalMethodsSection.java +++ b/codegen/plugins/client-codegen/src/main/java/software/amazon/smithy/java/codegen/client/sections/ClientInterfaceAdditionalMethodsSection.java @@ -12,6 +12,5 @@ * This section provides a hook for adding additional methods to the generated client interface. * * @param client Client shape that these methods will be added to. - * @param async true if the client is an async client. */ -public record ClientInterfaceAdditionalMethodsSection(ServiceShape client, boolean async) implements CodeSection {} +public record ClientInterfaceAdditionalMethodsSection(ServiceShape client) implements CodeSection {} diff --git a/codegen/plugins/client-codegen/src/test/java/software/amazon/smithy/java/codegen/client/TestAuthScheme.java b/codegen/plugins/client-codegen/src/test/java/software/amazon/smithy/java/codegen/client/TestAuthScheme.java index 399c59a42..c824c5b04 100644 --- a/codegen/plugins/client-codegen/src/test/java/software/amazon/smithy/java/codegen/client/TestAuthScheme.java +++ b/codegen/plugins/client-codegen/src/test/java/software/amazon/smithy/java/codegen/client/TestAuthScheme.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.codegen.client; -import java.util.concurrent.CompletableFuture; import software.amazon.smithy.java.auth.api.Signer; import software.amazon.smithy.java.auth.api.identity.Identity; import software.amazon.smithy.java.auth.api.identity.IdentityResolver; @@ -53,13 +52,8 @@ public Signer signer() { private static final class TestSigner implements Signer { @Override - public CompletableFuture sign( - HttpRequest request, - Identity identity, - Context properties - ) { - return CompletableFuture.completedFuture( - request.toBuilder().withAddedHeader(SIGNATURE_HEADER, "smithy-test-signature").build()); + public HttpRequest sign(HttpRequest request, Identity identity, Context properties) { + return request.toBuilder().withAddedHeader(SIGNATURE_HEADER, "smithy-test-signature").build(); } } diff --git a/core/src/main/java/software/amazon/smithy/java/core/serde/TimestampFormatter.java b/core/src/main/java/software/amazon/smithy/java/core/serde/TimestampFormatter.java index 9f88726b6..b5216b23e 100644 --- a/core/src/main/java/software/amazon/smithy/java/core/serde/TimestampFormatter.java +++ b/core/src/main/java/software/amazon/smithy/java/core/serde/TimestampFormatter.java @@ -159,25 +159,17 @@ public Instant readFromString(String value, boolean strict) { @Override public Instant readFromNumber(Number value) { // The most common types for serialized epoch-seconds, double/integer/long, are checked first. - if (value instanceof Double f) { - return Instant.ofEpochMilli((long) (f * 1000f)); - } else if (value instanceof Integer i) { - return Instant.ofEpochMilli(i * 1000L); - } else if (value instanceof Long l) { - return Instant.ofEpochMilli(l * 1000L); - } else if (value instanceof Byte b) { - return Instant.ofEpochMilli(b * 1000L); - } else if (value instanceof Short s) { - return Instant.ofEpochMilli(s * 1000L); - } else if (value instanceof Float f) { - return Instant.ofEpochMilli((long) (f * 1000f)); - } else if (value instanceof BigInteger bi) { - return Instant.ofEpochMilli(bi.longValue() * 1000); - } else if (value instanceof BigDecimal bd) { - return Instant.ofEpochMilli(bd.longValue() * 1000); - } else { - throw new TimestampSyntaxError(format(), ExpectedType.NUMBER, value); - } + return switch (value) { + case Double f -> Instant.ofEpochMilli((long) (f * 1000f)); + case Integer i -> Instant.ofEpochMilli(i * 1000L); + case Long l -> Instant.ofEpochMilli(l * 1000L); + case Byte b -> Instant.ofEpochMilli(b * 1000L); + case Short s -> Instant.ofEpochMilli(s * 1000L); + case Float f -> Instant.ofEpochMilli((long) (f * 1000f)); + case BigInteger bi -> Instant.ofEpochMilli(bi.longValue() * 1000); + case BigDecimal bd -> Instant.ofEpochMilli(bd.longValue() * 1000); + case null, default -> throw new TimestampSyntaxError(format(), ExpectedType.NUMBER, value); + }; } @Override diff --git a/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java b/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java index 7e11bf7a1..2f4dd45f0 100644 --- a/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java +++ b/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java @@ -13,6 +13,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import software.amazon.smithy.java.core.schema.PreludeSchemas; import software.amazon.smithy.java.core.schema.Schema; @@ -636,40 +637,33 @@ static Document of(Map members) { * @throws UnsupportedOperationException if the given object {@code o} cannot be converted to a document. */ static Document ofObject(Object o) { - if (o instanceof Document d) { - return d; - } else if (o instanceof SerializableShape s) { - return of(s); - } else if (o instanceof String s) { - return of(s); - } else if (o instanceof Boolean b) { - return of(b); - } else if (o instanceof Number n) { - return ofNumber(n); - } else if (o instanceof ByteBuffer b) { - return of(b); - } else if (o instanceof byte[] b) { - return of(b); - } else if (o instanceof Instant i) { - return of(i); - } else if (o instanceof List l) { - List values = new ArrayList<>(l.size()); - for (var v : l) { - values.add(ofObject(v)); + return switch (o) { + case Document d -> d; + case SerializableShape s -> of(s); + case String s -> of(s); + case Boolean b -> of(b); + case Number n -> ofNumber(n); + case ByteBuffer b -> of(b); + case byte[] b -> of(b); + case Instant i -> of(i); + case List l -> { + List values = new ArrayList<>(l.size()); + for (var v : l) { + values.add(ofObject(v)); + } + yield of(values); } - return of(values); - } else if (o instanceof Map m) { - Map values = new LinkedHashMap<>(m.size()); - for (var entry : m.entrySet()) { - var key = ofObject(entry.getKey()); - values.put(key.asString(), ofObject(entry.getValue())); + case Map m -> { + Map values = new LinkedHashMap<>(m.size()); + for (var entry : m.entrySet()) { + var key = Objects.requireNonNull(ofObject(entry.getKey())); + values.put(key.asString(), ofObject(entry.getValue())); + } + yield of(values); } - return of(values); - } else if (o == null) { - return null; - } else { - throw new UnsupportedOperationException("Cannot convert " + o + " to a document"); - } + case null -> null; + default -> throw new UnsupportedOperationException("Cannot convert " + o + " to a document"); + }; } /** diff --git a/examples/dynamodb-client/src/jmh/java/software/amazon/smithy/java/example/dynamodb/DynamoDBSerde.java b/examples/dynamodb-client/src/jmh/java/software/amazon/smithy/java/example/dynamodb/DynamoDBSerde.java index a4fa2d93c..8d1c70570 100644 --- a/examples/dynamodb-client/src/jmh/java/software/amazon/smithy/java/example/dynamodb/DynamoDBSerde.java +++ b/examples/dynamodb-client/src/jmh/java/software/amazon/smithy/java/example/dynamodb/DynamoDBSerde.java @@ -54,10 +54,9 @@ public void putItem(PutItemState s, Blackhole bh) { } @Benchmark - public void getItem(GetItemState s, Blackhole bh) throws Exception { + public void getItem(GetItemState s, Blackhole bh) { var resp = fullResponse(s.testItem.utf8); - var result = s.protocol.deserializeResponse(s.operation, s.context, s.operation.errorRegistry(), s.req, resp) - .get(); + var result = s.protocol.deserializeResponse(s.operation, s.context, s.operation.errorRegistry(), s.req, resp); bh.consume(result); } diff --git a/examples/restjson-client/src/it/java/software/amazon/smithy/java/example/ClientConfigTest.java b/examples/restjson-client/src/it/java/software/amazon/smithy/java/example/ClientConfigTest.java index 150c5720f..0d67b9918 100644 --- a/examples/restjson-client/src/it/java/software/amazon/smithy/java/example/ClientConfigTest.java +++ b/examples/restjson-client/src/it/java/software/amazon/smithy/java/example/ClientConfigTest.java @@ -10,7 +10,6 @@ import java.net.http.HttpClient; import java.time.Instant; import java.util.List; -import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.aws.client.restjson.RestJsonClientProtocol; @@ -133,17 +132,17 @@ public PersonDirectoryClientWithDefaults(PersonDirectoryClientWithDefaults.Build @Override public GetPersonImageOutput getPersonImage(GetPersonImageInput input, RequestOverrideConfig overrideConfig) { - return call(input, GetPersonImage.instance(), overrideConfig).join(); + return call(input, GetPersonImage.instance(), overrideConfig); } @Override public PutPersonOutput putPerson(PutPersonInput input, RequestOverrideConfig overrideConfig) { - return call(input, PutPerson.instance(), overrideConfig).join(); + return call(input, PutPerson.instance(), overrideConfig); } @Override public PutPersonImageOutput putPersonImage(PutPersonImageInput input, RequestOverrideConfig overrideConfig) { - return call(input, PutPersonImage.instance(), overrideConfig).join(); + return call(input, PutPersonImage.instance(), overrideConfig); } static PersonDirectoryClientWithDefaults.Builder builder() { @@ -189,12 +188,9 @@ public void configureClient(ClientConfig.Builder config) { static final class RegionalEndpointResolver implements EndpointResolver { @Override - public CompletableFuture resolveEndpoint(EndpointResolverParams params) { + public Endpoint resolveEndpoint(EndpointResolverParams params) { String region = params.context().get(REGION); - return CompletableFuture.completedFuture( - Endpoint.builder() - .uri("http://" + region + ".example.com") - .build()); + return Endpoint.builder().uri("http://" + region + ".example.com").build(); } } } diff --git a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/HttpBindingDeserializer.java b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/HttpBindingDeserializer.java index 8fb28b199..6a030834f 100644 --- a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/HttpBindingDeserializer.java +++ b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/HttpBindingDeserializer.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Flow; import software.amazon.smithy.java.core.schema.Schema; import software.amazon.smithy.java.core.schema.SerializableStruct; @@ -43,7 +42,6 @@ final class HttpBindingDeserializer extends SpecificShapeDeserializer implements private final BindingMatcher bindingMatcher; private final DataStream body; private final EventDecoderFactory eventDecoderFactory; - private CompletableFuture bodyDeserializationCf; private final String payloadMediaType; private HttpBindingDeserializer(Builder builder) { @@ -130,11 +128,10 @@ public DataStream readDataStream(Schema schema) { }); } else if (member.type() == ShapeType.STRUCTURE || member.type() == ShapeType.UNION) { // Read the payload into a byte buffer to deserialize a shape in the body. - bodyDeserializationCf = bodyAsByteBuffer().thenAccept(bb -> { - if (bb.remaining() > 0) { - structMemberConsumer.accept(state, member, payloadCodec.createDeserializer(bb)); - } - }).toCompletableFuture(); + ByteBuffer bb = bodyAsByteBuffer(); + if (bb.remaining() > 0) { + structMemberConsumer.accept(state, member, payloadCodec.createDeserializer(bb)); + } } else if (body != null && body.contentLength() > 0) { structMemberConsumer.accept(state, member, new PayloadDeserializer(payloadCodec, body)); } @@ -150,12 +147,11 @@ public DataStream readDataStream(Schema schema) { if (bindingMatcher.hasBody()) { validateMediaType(); // Need to read the entire payload into a byte buffer to deserialize via a codec. - bodyDeserializationCf = bodyAsByteBuffer().thenAccept(bb -> { - payloadCodec.createDeserializer(bb).readStruct(schema, bindingMatcher, (body, m, de) -> { - if (bindingMatcher.match(m) == BindingMatcher.Binding.BODY) { - structMemberConsumer.accept(state, m, de); - } - }); + ByteBuffer bb = bodyAsByteBuffer(); + payloadCodec.createDeserializer(bb).readStruct(schema, bindingMatcher, (body, m, de) -> { + if (bindingMatcher.match(m) == BindingMatcher.Binding.BODY) { + structMemberConsumer.accept(state, m, de); + } }); } } @@ -165,15 +161,8 @@ private static boolean isEventStream(Schema member) { } // TODO: Should there be a configurable limit on the client/server for how much can be read in memory? - private CompletableFuture bodyAsByteBuffer() { - return body.asByteBuffer(); - } - - CompletableFuture completeBodyDeserialization() { - if (bodyDeserializationCf == null) { - return CompletableFuture.completedFuture(null); - } - return bodyDeserializationCf; + private ByteBuffer bodyAsByteBuffer() { + return body.waitForByteBuffer(); } private void validateMediaType() { diff --git a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/PayloadDeserializer.java b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/PayloadDeserializer.java index 8e80394ca..357011c53 100644 --- a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/PayloadDeserializer.java +++ b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/PayloadDeserializer.java @@ -10,10 +10,8 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.concurrent.ExecutionException; import software.amazon.smithy.java.core.schema.Schema; import software.amazon.smithy.java.core.serde.Codec; -import software.amazon.smithy.java.core.serde.SerializationException; import software.amazon.smithy.java.core.serde.ShapeDeserializer; import software.amazon.smithy.java.core.serde.document.Document; import software.amazon.smithy.java.io.datastream.DataStream; @@ -28,11 +26,7 @@ final class PayloadDeserializer implements ShapeDeserializer { } private ByteBuffer resolveBodyBytes() { - try { - return body.asByteBuffer().get(); - } catch (InterruptedException | ExecutionException e) { - throw new SerializationException("Failed to get payload bytes", e); - } + return body.waitForByteBuffer(); } private ShapeDeserializer createDeserializer() { @@ -125,17 +119,10 @@ public String readString(Schema schema) { return null; } - try { - return body.asByteBuffer() - .thenApply(buffer -> { - int pos = buffer.arrayOffset() + buffer.position(); - int len = buffer.remaining(); - return new String(buffer.array(), pos, len, StandardCharsets.UTF_8); - }) - .get(); - } catch (InterruptedException | ExecutionException e) { - throw new SerializationException("Failed to get payload bytes", e); - } + var buffer = body.waitForByteBuffer(); + int pos = buffer.arrayOffset() + buffer.position(); + int len = buffer.remaining(); + return new String(buffer.array(), pos, len, StandardCharsets.UTF_8); } @Override diff --git a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/RequestDeserializer.java b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/RequestDeserializer.java index 90fa801eb..620e7ecaa 100644 --- a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/RequestDeserializer.java +++ b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/RequestDeserializer.java @@ -6,7 +6,6 @@ package software.amazon.smithy.java.http.binding; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; import software.amazon.smithy.java.core.schema.Schema; import software.amazon.smithy.java.core.schema.ShapeBuilder; @@ -99,7 +98,7 @@ public RequestDeserializer pathLabelValues(Map labelValues) { /** * Finish setting up and deserialize the response into the builder. */ - public CompletableFuture deserialize() { + public void deserialize() { if (inputShapeBuilder == null) { throw new IllegalStateException("inputShapeBuilder must be set"); } @@ -109,6 +108,5 @@ public CompletableFuture deserialize() { HttpBindingDeserializer deserializer = deserBuilder.build(); inputShapeBuilder.deserialize(deserializer); - return deserializer.completeBodyDeserialization(); } } diff --git a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/ResponseDeserializer.java b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/ResponseDeserializer.java index 17456c7ea..1e4beacd5 100644 --- a/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/ResponseDeserializer.java +++ b/http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/ResponseDeserializer.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.http.binding; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; import software.amazon.smithy.java.core.error.ModeledException; import software.amazon.smithy.java.core.schema.Schema; @@ -114,7 +113,7 @@ public ResponseDeserializer errorShapeBuilder(ShapeBuilder deserialize() { + public void deserialize() { Schema schema; if (outputShapeBuilder != null) { schema = outputShapeBuilder.schema(); @@ -128,13 +127,7 @@ public CompletableFuture deserialize() { deserBuilder.bindingMatcher(matcher); HttpBindingDeserializer deserializer = deserBuilder.build(); - - if (outputShapeBuilder != null) { - outputShapeBuilder.deserialize(deserializer); - } else { - errorShapeBuilder.deserialize(deserializer); - } - - return deserializer.completeBodyDeserialization(); + var target = outputShapeBuilder != null ? outputShapeBuilder : errorShapeBuilder; + target.deserialize(deserializer); } } diff --git a/io/src/test/java/software/amazon/smithy/java/io/datastream/FileDataStreamTest.java b/io/src/test/java/software/amazon/smithy/java/io/datastream/FileDataStreamTest.java index 3f81980ba..223f98afd 100644 --- a/io/src/test/java/software/amazon/smithy/java/io/datastream/FileDataStreamTest.java +++ b/io/src/test/java/software/amazon/smithy/java/io/datastream/FileDataStreamTest.java @@ -21,7 +21,7 @@ public void createsFromFile() throws Exception { assertThat(ds.contentLength(), equalTo(6L)); assertThat(ds.contentType(), equalTo("text/plain")); - assertThat(ds.asByteBuffer().get(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); + assertThat(ds.waitForByteBuffer(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); assertThat(ds.isReplayable(), is(true)); } @@ -31,7 +31,7 @@ public void createsFromFileWithMetadata() throws Exception { assertThat(ds.contentLength(), equalTo(6L)); assertThat(ds.contentType(), equalTo("text/foo")); - assertThat(ds.asByteBuffer().get(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); + assertThat(ds.waitForByteBuffer(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); } @Test diff --git a/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java b/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java index a4e3007e8..3ef9430b0 100644 --- a/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java +++ b/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java @@ -23,7 +23,7 @@ public void createsInputStreamDataStream() throws Exception { assertThat(ds.contentLength(), equalTo(-1L)); assertThat(ds.contentType(), nullValue()); - assertThat(ds.asByteBuffer().get(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); + assertThat(ds.waitForByteBuffer(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); assertThat(ds.isReplayable(), is(false)); } @@ -36,7 +36,7 @@ public void createsInputStreamDataStreamWithMetadata() throws Exception { assertThat(ds.contentLength(), equalTo(6L)); assertThat(ds.contentType(), equalTo("text/plain")); - assertThat(ds.asByteBuffer().get(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); + assertThat(ds.waitForByteBuffer(), equalTo(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); } @Test diff --git a/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientRequestProtocolTestProvider.java b/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientRequestProtocolTestProvider.java index 74fce5447..fa1894954 100644 --- a/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientRequestProtocolTestProvider.java +++ b/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientRequestProtocolTestProvider.java @@ -7,7 +7,6 @@ import java.util.Base64; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.extension.Extension; @@ -182,9 +181,9 @@ public HttpRequest getCapturedRequest() { } @Override - public CompletableFuture send(Context context, HttpRequest request) { + public HttpResponse send(Context context, HttpRequest request) { this.capturedRequest = request; - return CompletableFuture.completedFuture(exceptionalResponse); + return exceptionalResponse; } @Override diff --git a/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientResponseProtocolTestProvider.java b/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientResponseProtocolTestProvider.java index c07cb4a21..ee65e2aa3 100644 --- a/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientResponseProtocolTestProvider.java +++ b/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/HttpClientResponseProtocolTestProvider.java @@ -12,7 +12,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; @@ -143,7 +142,7 @@ private record TestTransport(HttpResponseTestCase testCase) implements ClientTransport { @Override - public CompletableFuture send(Context context, HttpRequest request) { + public HttpResponse send(Context context, HttpRequest request) { var builder = HttpResponse.builder() .httpVersion(HttpVersion.HTTP_1_1) .statusCode(testCase.getCode()); @@ -170,7 +169,7 @@ public CompletableFuture send(Context context, HttpRequest request } }); - return CompletableFuture.completedFuture(builder.build()); + return builder.build(); } @Override diff --git a/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/MockClient.java b/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/MockClient.java index f78e9797a..96c32b6ac 100644 --- a/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/MockClient.java +++ b/protocol-test-harness/src/main/java/software/amazon/smithy/java/protocoltests/harness/MockClient.java @@ -7,8 +7,6 @@ import java.net.URI; import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import software.amazon.smithy.java.client.core.Client; import software.amazon.smithy.java.client.core.ClientProtocol; import software.amazon.smithy.java.client.core.ClientTransport; @@ -52,20 +50,22 @@ public O clientRequ RequestOverrideConfig overrideConfig ) { try { - return call(input, operation, overrideConfig).exceptionallyCompose(exc -> { - if (exc instanceof CompletionException ce - && ce.getCause() instanceof CallException apiException - && apiException.getFault().equals(ErrorFault.SERVER) - && exc.getMessage().contains(SYNTHETIC_ERROR_SEARCH)) { - LOGGER.debug("Ignoring expected exception", apiException); - return CompletableFuture.completedFuture(null); + return call(input, operation, overrideConfig); + } catch (CallException apiException) { + if (apiException.getFault().equals(ErrorFault.SERVER)) { + String message = apiException.getMessage(); + if (message != null) { + if (message.contains(SYNTHETIC_ERROR_SEARCH)) { + LOGGER.debug("Ignoring expected exception", apiException); + return null; + } } else { - LOGGER.error("Encountered Unexpected exception", exc); - return CompletableFuture.failedFuture(exc); + apiException.printStackTrace(); } - }).join(); - } catch (CompletionException e) { - throw unwrapAndThrow(e); + } + + LOGGER.error("Encountered Unexpected exception", apiException); + throw apiException; } } @@ -117,7 +117,7 @@ public Req setServiceEndpoint(Req request, Endpoint endpoint) { } @Override - public CompletableFuture deserializeResponse( + public O deserializeResponse( ApiOperation operation, Context context, TypeRegistry typeRegistry, @@ -149,7 +149,7 @@ private ClientTransport getTransport() { } @Override - public CompletableFuture send(Context context, Req request) { + public Res send(Context context, Req request) { return getTransport().send(context, request); }