diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-4ded69a.json b/.changes/next-release/bugfix-AWSSDKforJavav2-4ded69a.json new file mode 100644 index 000000000000..6728940d0795 --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-4ded69a.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "This changes fixes the previously incorrect handling of `hostPrefix` in services, causing requests for some services to have the wrong URL. This change also undoes the suppression of rules-based endpoint generation for all services apart from EventBridge, S3, and S3 Control, which was implemented in [#3520](https://github.com/aws/aws-sdk-java-v2/issues/3520) because of the previously mentioned `hostPrefix` issue." +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java index f5b5a8b13bd9..9707f7612b32 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import software.amazon.awssdk.codegen.emitters.GeneratorTask; @@ -46,10 +45,6 @@ public EndpointProviderTasks(GeneratorTaskParams dependencies) { @Override protected List createTasks() throws Exception { - if (!generatorTaskParams.getModel().getCustomizationConfig().useRuleBasedEndpoints()) { - return Collections.emptyList(); - } - List tasks = new ArrayList<>(); tasks.add(generateInterface()); tasks.add(generateParams()); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java index d062f5057c9d..4bcd436c06c9 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java @@ -213,11 +213,6 @@ public class CustomizationConfig { private boolean useGlobalEndpoint; - /** - * Whether Endpoints 2.0/rule based endpoints should be used for endpoint resolution. - */ - private boolean useRuleBasedEndpoints = false; - private List interceptors = new ArrayList<>(); private CustomizationConfig() { @@ -557,14 +552,6 @@ public void setSkipEndpointTests(Map skipEndpointTests) { this.skipEndpointTests = skipEndpointTests; } - public boolean useRuleBasedEndpoints() { - return useRuleBasedEndpoints; - } - - public void setUseRuleBasedEndpoints(boolean useRuleBasedEndpoints) { - this.useRuleBasedEndpoints = useRuleBasedEndpoints; - } - public List getInterceptors() { return interceptors; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java index 84b02b295b21..ff003afa5bdd 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java @@ -69,9 +69,7 @@ public TypeSpec poetSpec() { } } - if (endpointRulesSpecUtils.isEndpointRulesEnabled()) { - builder.addMethod(endpointProviderMethod()); - } + builder.addMethod(endpointProviderMethod()); if (AuthUtils.usesBearerAuth(model)) { builder.addMethod(bearerTokenProviderMethod()); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java index 0f065a6189fb..5048f1fd3e76 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java @@ -111,11 +111,9 @@ public TypeSpec poetSpec() { builder.addMethod(finalizeServiceConfigurationMethod()); defaultAwsAuthSignerMethod().ifPresent(builder::addMethod); builder.addMethod(signingNameMethod()); - if (endpointRulesSpecUtils.isEndpointRulesEnabled()) { - builder.addMethod(defaultEndpointProviderMethod()); - } + builder.addMethod(defaultEndpointProviderMethod()); - if (hasClientContextParams() && endpointRulesSpecUtils.isEndpointRulesEnabled()) { + if (hasClientContextParams()) { model.getClientContextParams().forEach((n, m) -> { builder.addMethod(clientContextParamSetter(n, m)); }); @@ -191,9 +189,8 @@ private MethodSpec mergeServiceDefaultsMethod() { .addParameter(SdkClientConfiguration.class, "config") .addCode("return config.merge(c -> c"); - if (endpointRulesSpecUtils.isEndpointRulesEnabled()) { - builder.addCode(".option($T.ENDPOINT_PROVIDER, defaultEndpointProvider())", SdkClientOption.class); - } + builder.addCode(".option($T.ENDPOINT_PROVIDER, defaultEndpointProvider())", SdkClientOption.class); + if (defaultAwsAuthSignerMethod().isPresent()) { builder.addCode(".option($T.SIGNER, defaultSigner())\n", SdkAdvancedClientOption.class); @@ -261,11 +258,9 @@ private MethodSpec finalizeServiceConfigurationMethod() { List builtInInterceptors = new ArrayList<>(); - if (endpointRulesSpecUtils.isEndpointRulesEnabled()) { - builtInInterceptors.add(endpointRulesSpecUtils.resolverInterceptorName()); - builtInInterceptors.add(endpointRulesSpecUtils.authSchemesInterceptorName()); - builtInInterceptors.add(endpointRulesSpecUtils.requestModifierInterceptorName()); - } + builtInInterceptors.add(endpointRulesSpecUtils.resolverInterceptorName()); + builtInInterceptors.add(endpointRulesSpecUtils.authSchemesInterceptorName()); + builtInInterceptors.add(endpointRulesSpecUtils.requestModifierInterceptorName()); for (String interceptor : model.getCustomizationConfig().getInterceptors()) { builtInInterceptors.add(ClassName.bestGuess(interceptor)); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java index e56c8930b683..8e508d20b2cf 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java @@ -38,7 +38,6 @@ import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.utils.internal.CodegenNamingUtils; - public class BaseClientBuilderInterface implements ClassSpec { private final IntermediateModel model; private final String basePackage; @@ -73,14 +72,12 @@ public TypeSpec poetSpec() { builder.addMethod(serviceConfigurationConsumerBuilderMethod()); } - if (endpointRulesSpecUtils.isEndpointRulesEnabled()) { - builder.addMethod(endpointProviderMethod()); + builder.addMethod(endpointProviderMethod()); - if (hasClientContextParams()) { - model.getClientContextParams().forEach((n, m) -> { - builder.addMethod(clientContextParamSetter(n, m)); - }); - } + if (hasClientContextParams()) { + model.getClientContextParams().forEach((n, m) -> { + builder.addMethod(clientContextParamSetter(n, m)); + }); } if (generateTokenProviderMethod()) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java index 89ea95c189ba..e79b7a226bc0 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java @@ -69,9 +69,7 @@ public TypeSpec poetSpec() { } } - if (endpointRulesSpecUtils.isEndpointRulesEnabled()) { - builder.addMethod(endpointProviderMethod()); - } + builder.addMethod(endpointProviderMethod()); if (AuthUtils.usesBearerAuth(model)) { builder.addMethod(tokenProviderMethodImpl()); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointProviderTestSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointProviderTestSpec.java index cc1e9db37a85..08e07af1c9ce 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointProviderTestSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointProviderTestSpec.java @@ -88,7 +88,7 @@ private MethodSpec testsCasesMethod() { b.addStatement("testCases.add(new $T($L, $L))", EndpointProviderTestCase.class, createTestCase(test), - TestGeneratorUtils.createExpect(test.getExpect())); + TestGeneratorUtils.createExpect(test.getExpect(), null, null)); }); b.addStatement("return testCases"); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java index cb2d7c349e59..674fe01d1cb2 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java @@ -20,7 +20,9 @@ import com.fasterxml.jackson.jr.stree.JrsString; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; +import java.util.Iterator; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletionException; @@ -32,6 +34,8 @@ import software.amazon.awssdk.codegen.model.rules.endpoints.ParameterModel; import software.amazon.awssdk.codegen.model.service.ClientContextParam; import software.amazon.awssdk.codegen.model.service.ContextParam; +import software.amazon.awssdk.codegen.model.service.EndpointTrait; +import software.amazon.awssdk.codegen.model.service.HostPrefixProcessor; import software.amazon.awssdk.codegen.model.service.StaticContextParam; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetExtension; @@ -41,9 +45,12 @@ import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.HostnameValidator; +import software.amazon.awssdk.utils.StringUtils; public class EndpointResolverInterceptorSpec implements ClassSpec { private final IntermediateModel model; @@ -76,6 +83,8 @@ public TypeSpec poetSpec() { b.addMethod(setClientContextParamsMethod()); } + b.addMethod(hostPrefixMethod()); + return b.build(); } @@ -106,6 +115,15 @@ private MethodSpec modifyRequestMethod() { b.beginControlFlow("try"); b.addStatement("$T result = $N.resolveEndpoint(ruleParams(context, executionAttributes)).join()", Endpoint.class, providerVar); + b.beginControlFlow("if (!$T.disableHostPrefixInjection(executionAttributes))", + endpointRulesSpecUtils.rulesRuntimeClassName("AwsEndpointProviderUtils")); + b.addStatement("$T hostPrefix = hostPrefix(executionAttributes.getAttribute($T.OPERATION_NAME), context.request())", + ParameterizedTypeName.get(Optional.class, String.class), SdkExecutionAttribute.class); + b.beginControlFlow("if (hostPrefix.isPresent())"); + b.addStatement("result = $T.addHostPrefix(result, hostPrefix.get())", + endpointRulesSpecUtils.rulesRuntimeClassName("AwsEndpointProviderUtils")); + b.endControlFlow(); + b.endControlFlow(); b.addStatement("executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, result)"); b.addStatement("return context.request()"); b.endControlFlow(); @@ -171,7 +189,7 @@ private MethodSpec ruleParams() { case AWS_S3_FORCE_PATH_STYLE: case AWS_S3_USE_ARN_REGION: case AWS_S3_CONTROL_USE_ARN_REGION: - // end of S3 specific builtins + // end of S3 specific builtins case AWS_STS_USE_GLOBAL_ENDPOINT: // V2 doesn't support this, only regional endpoints return; @@ -361,4 +379,66 @@ private MethodSpec setClientContextParamsMethod() { return b.build(); } + + + private MethodSpec hostPrefixMethod() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("hostPrefix") + .returns(ParameterizedTypeName.get(Optional.class, String.class)) + .addParameter(String.class, "operationName") + .addParameter(SdkRequest.class, "request") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC); + + builder.beginControlFlow("switch (operationName)"); + + model.getOperations().forEach((name, opModel) -> { + String hostPrefix = getHostPrefix(opModel); + if (StringUtils.isBlank(hostPrefix)) { + return; + } + + builder.beginControlFlow("case $S:", name); + HostPrefixProcessor processor = new HostPrefixProcessor(hostPrefix); + + if (processor.c2jNames().isEmpty()) { + builder.addStatement("return $T.of($S)", Optional.class, processor.hostWithStringSpecifier()); + } else { + String requestVar = opModel.getInput().getVariableName(); + processor.c2jNames().forEach(c2jName -> { + builder.addStatement("$1T.validateHostnameCompliant(request.getValueForField($2S, $3T.class).orElse(null), " + + "$2S, $4S)", + HostnameValidator.class, + c2jName, + String.class, + requestVar); + }); + + builder.addCode("return $T.of($T.format($S, ", Optional.class, String.class, + processor.hostWithStringSpecifier()); + Iterator c2jNamesIter = processor.c2jNames().listIterator(); + while (c2jNamesIter.hasNext()) { + builder.addCode("request.getValueForField($S, $T.class).get()", c2jNamesIter.next(), String.class); + if (c2jNamesIter.hasNext()) { + builder.addCode(","); + } + } + builder.addStatement("))"); + } + builder.endControlFlow(); + }); + + builder.addCode("default:"); + builder.addStatement("return $T.empty()", Optional.class); + builder.endControlFlow(); + + return builder.build(); + } + + private String getHostPrefix(OperationModel opModel) { + EndpointTrait endpointTrait = opModel.getEndpointTrait(); + if (endpointTrait == null) { + return null; + } + + return endpointTrait.getHostPrefix(); + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java index d50cae1a009d..f6938bf2558c 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesClientTestSpec.java @@ -15,6 +15,8 @@ package software.amazon.awssdk.codegen.poet.rules; +import static software.amazon.awssdk.codegen.poet.rules.TestGeneratorUtils.getHostPrefixTemplate; + import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.jr.stree.JrsArray; import com.fasterxml.jackson.jr.stree.JrsObject; @@ -56,6 +58,7 @@ import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetExtension; import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.codegen.utils.AuthUtils; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.rules.testing.AsyncTestCase; @@ -217,7 +220,8 @@ private MethodSpec syncTestsSourceMethod() { SyncTestCase.class, test.getDocumentation(), syncOperationCallLambda(opModel, test.getParams(), opInput.getOperationParams()), - TestGeneratorUtils.createExpect(test.getExpect()), getSkipReasonBlock(test.getDocumentation())); + TestGeneratorUtils.createExpect(test.getExpect(), opModel, opInput.getOperationParams()), + getSkipReasonBlock(test.getDocumentation())); if (operationInputsIter.hasNext()) { b.addCode(","); @@ -228,7 +232,8 @@ private MethodSpec syncTestsSourceMethod() { SyncTestCase.class, test.getDocumentation(), syncOperationCallLambda(defaultOpModel, test.getParams(), Collections.emptyMap()), - TestGeneratorUtils.createExpect(test.getExpect()), getSkipReasonBlock(test.getDocumentation())); + TestGeneratorUtils.createExpect(test.getExpect(), defaultOpModel, null), + getSkipReasonBlock(test.getDocumentation())); } if (testIter.hasNext()) { @@ -248,6 +253,9 @@ private CodeBlock syncOperationCallLambda(OperationModel opModel, Map "); b.addStatement("$T builder = $T.builder()", syncClientBuilder(), syncClientClass()); b.addStatement("builder.credentialsProvider($T.CREDENTIALS_PROVIDER)", BaseRuleSetClientTest.class); + if (AuthUtils.usesBearerAuth(model)) { + b.addStatement("builder.tokenProvider($T.TOKEN_PROVIDER)", BaseRuleSetClientTest.class); + } b.addStatement("builder.httpClient(getSyncHttpClient())"); if (params != null) { @@ -272,6 +280,9 @@ private CodeBlock asyncOperationCallLambda(OperationModel opModel, Map "); b.addStatement("$T builder = $T.builder()", asyncClientBuilder(), asyncClientClass()); b.addStatement("builder.credentialsProvider($T.CREDENTIALS_PROVIDER)", BaseRuleSetClientTest.class); + if (AuthUtils.usesBearerAuth(model)) { + b.addStatement("builder.tokenProvider($T.TOKEN_PROVIDER)", BaseRuleSetClientTest.class); + } b.addStatement("builder.httpClient(getAsyncHttpClient())"); if (params != null) { @@ -355,7 +366,8 @@ private MethodSpec asyncTestsSourceMethod() { AsyncTestCase.class, test.getDocumentation(), asyncOperationCallLambda(opModel, test.getParams(), opInput.getOperationParams()), - TestGeneratorUtils.createExpect(test.getExpect()), getSkipReasonBlock(test.getDocumentation())); + TestGeneratorUtils.createExpect(test.getExpect(), opModel, opInput.getOperationParams()), + getSkipReasonBlock(test.getDocumentation())); if (operationInputsIter.hasNext()) { b.addCode(","); @@ -366,7 +378,8 @@ private MethodSpec asyncTestsSourceMethod() { AsyncTestCase.class, test.getDocumentation(), asyncOperationCallLambda(defaultOpModel, test.getParams(), Collections.emptyMap()), - TestGeneratorUtils.createExpect(test.getExpect()), getSkipReasonBlock(test.getDocumentation())); + TestGeneratorUtils.createExpect(test.getExpect(), defaultOpModel, null), + getSkipReasonBlock(test.getDocumentation())); } if (testIter.hasNext()) { @@ -399,8 +412,10 @@ private CodeBlock requestCreation(OperationModel opModel, Map return b.add(".build()").build(); } + String hostPrefix = getHostPrefixTemplate(opModel).orElse(""); + inputShape.getMembers().forEach(m -> { - if (!boundToPath(m)) { + if (!boundToPath(m) && !hostPrefix.contains("{" + m.getC2jName() + "}")) { return; } @@ -445,6 +460,12 @@ private static boolean canBeEmpty(OperationModel opModel) { return false; } + String hostPrefix = getHostPrefixTemplate(opModel).orElse(""); + + if (hostPrefix.contains("{") && hostPrefix.contains("}")) { + return false; + } + Optional pathMemberOrStreaming = members.stream() .filter(EndpointRulesClientTestSpec::boundToPath) .findFirst(); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java index 6babbfda4ce1..2f6b6bbba5fb 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java @@ -182,8 +182,4 @@ public boolean isS3Control() { public TypeName resolverReturnType() { return ParameterizedTypeName.get(CompletableFuture.class, Endpoint.class); } - - public boolean isEndpointRulesEnabled() { - return intermediateModel.getCustomizationConfig().useRuleBasedEndpoints(); - } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/TestGeneratorUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/TestGeneratorUtils.java index 96356c50b0dd..83e1e10a7f6f 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/TestGeneratorUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/TestGeneratorUtils.java @@ -22,22 +22,28 @@ import com.fasterxml.jackson.jr.stree.JrsValue; import com.squareup.javapoet.CodeBlock; import java.net.URI; +import java.net.URISyntaxException; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.codegen.model.intermediate.OperationModel; import software.amazon.awssdk.codegen.model.rules.endpoints.ExpectModel; +import software.amazon.awssdk.codegen.model.service.EndpointTrait; +import software.amazon.awssdk.codegen.model.service.HostPrefixProcessor; import software.amazon.awssdk.core.rules.testing.model.Expect; import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.utils.StringUtils; public final class TestGeneratorUtils { private TestGeneratorUtils() { } - public static CodeBlock createExpect(ExpectModel expect) { + public static CodeBlock createExpect(ExpectModel expect, OperationModel opModel, Map opParams) { CodeBlock.Builder b = CodeBlock.builder(); b.add("$T.builder()", Expect.class); @@ -48,9 +54,10 @@ public static CodeBlock createExpect(ExpectModel expect) { CodeBlock.Builder endpointBuilder = CodeBlock.builder(); ExpectModel.Endpoint endpoint = expect.getEndpoint(); + String expectedUrl = createExpectedUrl(endpoint, opModel, opParams); endpointBuilder.add("$T.builder()", Endpoint.class); - endpointBuilder.add(".url($T.create($S))", URI.class, endpoint.getUrl()); + endpointBuilder.add(".url($T.create($S))", URI.class, expectedUrl); if (endpoint.getHeaders() != null) { @@ -76,6 +83,16 @@ public static CodeBlock createExpect(ExpectModel expect) { return b.build(); } + public static Optional getHostPrefixTemplate(OperationModel opModel) { + EndpointTrait endpointTrait = opModel.getEndpointTrait(); + + if (endpointTrait == null) { + return Optional.empty(); + } + + return Optional.ofNullable(endpointTrait.getHostPrefix()); + } + private static void addEndpointAttributeBlock(CodeBlock.Builder builder, String attrName, TreeNode attrValue) { switch (attrName) { case "authSchemes": @@ -156,4 +173,56 @@ private static CodeBlock authSchemeCreationExpr(TreeNode attrValue) { return schemeExpr.build(); } + + + private static String createExpectedUrl(ExpectModel.Endpoint endpoint, + OperationModel opModel, + Map opParams) { + Optional prefix = getHostPrefix(opModel, opParams); + if (!prefix.isPresent()) { + return endpoint.getUrl(); + } + + URI originalUrl = URI.create(endpoint.getUrl()); + + try { + URI newUrl = new URI(originalUrl.getScheme(), + null, + prefix.get() + originalUrl.getHost(), + originalUrl.getPort(), + originalUrl.getPath(), + originalUrl.getQuery(), + originalUrl.getFragment()); + return newUrl.toString(); + } catch (URISyntaxException e) { + throw new RuntimeException("Expected url creation failed", e); + } + } + + private static Optional getHostPrefix(OperationModel opModel, Map opParams) { + if (opModel == null) { + return Optional.empty(); + } + + Optional hostPrefixTemplate = getHostPrefixTemplate(opModel); + + if (!hostPrefixTemplate.isPresent() || StringUtils.isBlank(hostPrefixTemplate.get())) { + return Optional.empty(); + } + + HostPrefixProcessor processor = new HostPrefixProcessor(hostPrefixTemplate.get()); + + String pattern = processor.hostWithStringSpecifier(); + + for (String c2jName : processor.c2jNames()) { + if (opParams != null && opParams.containsKey(c2jName)) { + String value = ((JrsString) opParams.get(c2jName)).getValue(); + pattern = StringUtils.replaceOnce(pattern, "%s", value); + } else { + pattern = StringUtils.replaceOnce(pattern, "%s", "aws"); + } + } + + return Optional.of(pattern); + } } diff --git a/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource b/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource index dcb6e3eaa61f..ee11d604d2a2 100644 --- a/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource +++ b/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource @@ -16,6 +16,7 @@ import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.utils.HostnameValidator; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.http.SdkHttpUtils; @@ -74,6 +75,34 @@ public final class AwsEndpointProviderUtils { return attrs.getOptionalAttribute(SdkInternalExecutionAttribute.IS_DISCOVERED_ENDPOINT).orElse(false); } + /** + * True if the the {@link SdkInternalExecutionAttribute#DISABLE_HOST_PREFIX_INJECTION} attribute is present and its + * value is {@code true}, {@code false} otherwise. + */ + public static boolean disableHostPrefixInjection(ExecutionAttributes attrs) { + return attrs.getOptionalAttribute(SdkInternalExecutionAttribute.DISABLE_HOST_PREFIX_INJECTION).orElse(false); + } + + /** + * Apply the given endpoint prefix to the endpoint. + */ + public static Endpoint addHostPrefix(Endpoint endpoint, String prefix) { + if (StringUtils.isBlank(prefix)) { + return endpoint; + } + + validatePrefixIsHostNameCompliant(prefix); + + URI originalUrl = endpoint.url(); + String newHost = prefix + endpoint.url().getHost(); + URI newUrl = invokeSafely(() -> new URI(originalUrl.getScheme(), null, newHost, originalUrl.getPort(), + originalUrl.getPath(), originalUrl.getQuery(), originalUrl.getFragment())); + + return endpoint.toBuilder() + .url(newUrl) + .build(); + } + public static Endpoint valueAsEndpointOrThrow(Value value) { if (value instanceof Value.Endpoint) { Value.Endpoint endpoint = value.expectEndpoint(); @@ -170,4 +199,15 @@ public final class AwsEndpointProviderUtils { } }); } + + private static void validatePrefixIsHostNameCompliant(String prefix) { + String[] components = splitHostLabelOnDots(prefix); + for (String component : components) { + HostnameValidator.validateHostnameCompliant(component, component, "request"); + } + } + + private static String[] splitHostLabelOnDots(String label) { + return label.split("\\."); + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java index 8217a63218c8..8973ffe68acf 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java @@ -5,6 +5,8 @@ import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** * Internal implementation of {@link JsonAsyncClientBuilder}. @@ -13,6 +15,12 @@ @SdkInternalApi final class DefaultJsonAsyncClientBuilder extends DefaultJsonBaseClientBuilder implements JsonAsyncClientBuilder { + @Override + public DefaultJsonAsyncClientBuilder endpointProvider(JsonEndpointProvider endpointProvider) { + clientConfiguration.option(SdkClientOption.ENDPOINT_PROVIDER, endpointProvider); + return this; + } + @Override public DefaultJsonAsyncClientBuilder tokenProvider(SdkTokenProvider tokenProvider) { clientConfiguration.option(AwsClientOption.TOKEN_PROVIDER, tokenProvider); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java index 87fb48f6c843..0c242955145e 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java @@ -15,6 +15,10 @@ import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; +import software.amazon.awssdk.services.json.endpoints.internal.JsonEndpointAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -36,7 +40,8 @@ protected final String serviceName() { @Override protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { - return config.merge(c -> c.option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) + return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) + .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) .option(AwsClientOption.TOKEN_PROVIDER, defaultTokenProvider()) .option(SdkAdvancedClientOption.TOKEN_SIGNER, defaultTokenSigner())); } @@ -44,6 +49,9 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati @Override protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new JsonResolveEndpointInterceptor()); + endpointInterceptors.add(new JsonEndpointAuthSchemeInterceptor()); + endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory .getInterceptors("software/amazon/awssdk/services/json/execution.interceptors"); @@ -59,6 +67,10 @@ protected final String signingName() { return "json-service"; } + private JsonEndpointProvider defaultEndpointProvider() { + return JsonEndpointProvider.defaultProvider(); + } + private SdkTokenProvider defaultTokenProvider() { return DefaultAwsTokenProvider.create(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java index e4749b5db707..9f85cd8f2492 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java @@ -3,6 +3,7 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** * This includes configuration specific to Json Service that is supported by both {@link JsonClientBuilder} and @@ -10,6 +11,12 @@ */ @Generated("software.amazon.awssdk:codegen") public interface JsonBaseClientBuilder, C> extends AwsClientBuilder { + /** + * Set the {@link JsonEndpointProvider} implementation that will be used by the client to determine the endpoint for + * each request. This is optional; if none is provided a default implementation will be used the SDK. + */ + B endpointProvider(JsonEndpointProvider endpointProvider); + /** * Set the token provider to use for bearer token authorization. This is optional, if none is provided, the SDK will * use {@link software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider}. @@ -22,4 +29,4 @@ public interface JsonBaseClientBuilder, C> * default it is {@link software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner}. */ B tokenProvider(SdkTokenProvider tokenProvider); -} \ No newline at end of file +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java index d1d57a440644..733f3969b891 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java @@ -19,6 +19,10 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.services.json.endpoints.JsonClientContextParams; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; +import software.amazon.awssdk.services.json.endpoints.internal.JsonEndpointAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -41,7 +45,8 @@ protected final String serviceName() { @Override protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { - return config.merge(c -> c.option(SdkAdvancedClientOption.SIGNER, defaultSigner()) + return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) + .option(SdkAdvancedClientOption.SIGNER, defaultSigner()) .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) .option(SdkClientOption.SERVICE_CONFIGURATION, ServiceConfiguration.builder().build()) .option(AwsClientOption.TOKEN_PROVIDER, defaultTokenProvider()) @@ -51,6 +56,9 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati @Override protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new JsonResolveEndpointInterceptor()); + endpointInterceptors.add(new JsonEndpointAuthSchemeInterceptor()); + endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory .getInterceptors("software/amazon/awssdk/services/json/execution.interceptors"); @@ -129,6 +137,10 @@ protected final String signingName() { return "json-service"; } + private JsonEndpointProvider defaultEndpointProvider() { + return JsonEndpointProvider.defaultProvider(); + } + public B serviceConfiguration(ServiceConfiguration serviceConfiguration) { clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION, serviceConfiguration); return thisBuilder(); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java index 06a4f0b1bf87..dd6652920acc 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java @@ -4,6 +4,7 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** * This includes configuration specific to Json Service that is supported by both {@link JsonClientBuilder} and @@ -17,6 +18,12 @@ default B serviceConfiguration(Consumer serviceCon return serviceConfiguration(ServiceConfiguration.builder().applyMutation(serviceConfiguration).build()); } + /** + * Set the {@link JsonEndpointProvider} implementation that will be used by the client to determine the endpoint for + * each request. This is optional; if none is provided a default implementation will be used the SDK. + */ + B endpointProvider(JsonEndpointProvider endpointProvider); + /** * Set the token provider to use for bearer token authorization. This is optional, if none is provided, the SDK will * use {@link software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider}. diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java index e4c39ff66108..71980c2daeb1 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java @@ -13,6 +13,10 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; +import software.amazon.awssdk.services.json.endpoints.internal.JsonEndpointAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -34,8 +38,9 @@ protected final String serviceName() { @Override protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { - return config.merge(c -> c.option(SdkAdvancedClientOption.SIGNER, defaultSigner()).option( - SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)); + return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) + .option(SdkAdvancedClientOption.SIGNER, defaultSigner()) + .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)); } @Override @@ -49,6 +54,9 @@ protected final SdkClientConfiguration mergeInternalDefaults(SdkClientConfigurat @Override protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new JsonResolveEndpointInterceptor()); + endpointInterceptors.add(new JsonEndpointAuthSchemeInterceptor()); + endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory .getInterceptors("software/amazon/awssdk/services/json/execution.interceptors"); @@ -68,6 +76,10 @@ protected final String signingName() { return "json-service"; } + private JsonEndpointProvider defaultEndpointProvider() { + return JsonEndpointProvider.defaultProvider(); + } + protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER), "The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder."); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java index e199235c3f28..141b27f6cfe0 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java @@ -18,6 +18,11 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor; +import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.services.query.endpoints.internal.QueryEndpointAuthSchemeInterceptor; +import software.amazon.awssdk.services.query.endpoints.internal.QueryRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -39,7 +44,8 @@ protected final String serviceName() { @Override protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { - return config.merge(c -> c.option(SdkAdvancedClientOption.SIGNER, defaultSigner()) + return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) + .option(SdkAdvancedClientOption.SIGNER, defaultSigner()) .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) .option(AwsClientOption.TOKEN_PROVIDER, defaultTokenProvider()) .option(SdkAdvancedClientOption.TOKEN_SIGNER, defaultTokenSigner())); @@ -48,6 +54,9 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati @Override protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new QueryResolveEndpointInterceptor()); + endpointInterceptors.add(new QueryEndpointAuthSchemeInterceptor()); + endpointInterceptors.add(new QueryRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory .getInterceptors("software/amazon/awssdk/services/query/execution.interceptors"); @@ -70,6 +79,20 @@ protected final String signingName() { return "query-service"; } + private QueryEndpointProvider defaultEndpointProvider() { + return QueryEndpointProvider.defaultProvider(); + } + + public B booleanContextParam(Boolean booleanContextParam) { + clientContextParams.put(QueryClientContextParams.BOOLEAN_CONTEXT_PARAM, booleanContextParam); + return thisBuilder(); + } + + public B stringContextParam(String stringContextParam) { + clientContextParams.put(QueryClientContextParams.STRING_CONTEXT_PARAM, stringContextParam); + return thisBuilder(); + } + private SdkTokenProvider defaultTokenProvider() { return DefaultAwsTokenProvider.create(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java index 88df8cb265d0..07c2d435e406 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java @@ -5,6 +5,8 @@ import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** * Internal implementation of {@link JsonClientBuilder}. @@ -13,6 +15,12 @@ @SdkInternalApi final class DefaultJsonClientBuilder extends DefaultJsonBaseClientBuilder implements JsonClientBuilder { + @Override + public DefaultJsonClientBuilder endpointProvider(JsonEndpointProvider endpointProvider) { + clientConfiguration.option(SdkClientOption.ENDPOINT_PROVIDER, endpointProvider); + return this; + } + @Override public DefaultJsonClientBuilder tokenProvider(SdkTokenProvider tokenProvider) { clientConfiguration.option(AwsClientOption.TOKEN_PROVIDER, tokenProvider); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java index b4b944dcf219..3d503315b160 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java @@ -10,6 +10,7 @@ import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; @@ -30,6 +31,13 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut .getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); try { Endpoint result = provider.resolveEndpoint(ruleParams(context, executionAttributes)).join(); + if (!AwsEndpointProviderUtils.disableHostPrefixInjection(executionAttributes)) { + Optional hostPrefix = hostPrefix(executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME), + context.request()); + if (hostPrefix.isPresent()) { + result = AwsEndpointProviderUtils.addHostPrefix(result, hostPrefix.get()); + } + } executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, result); return context.request(); } catch (CompletionException e) { @@ -88,4 +96,14 @@ private static void setClientContextParams(QueryEndpointParams.Builder params, E Optional.ofNullable(clientContextParams.get(QueryClientContextParams.STRING_CONTEXT_PARAM)).ifPresent( params::stringContextParam); } + + private static Optional hostPrefix(String operationName, SdkRequest request) { + switch (operationName) { + case "APostOperation": { + return Optional.of("foo-"); + } + default: + return Optional.empty(); + } + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-rules-test-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-rules-test-class.java index f33c93bb680b..de8688ea311d 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-rules-test-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-rules-test-class.java @@ -50,24 +50,27 @@ private static List syncTestCases() { new SyncTestCase("test case 1", () -> { QueryClientBuilder builder = QueryClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getSyncHttpClient()); builder.region(Region.of("us-east-1")); APostOperationRequest request = APostOperationRequest.builder().build(); builder.build().aPostOperation(request); - }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://myservice.aws")).build()).build()), + }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://foo-myservice.aws")).build()).build()), new SyncTestCase("test case 2", () -> { QueryClientBuilder builder = QueryClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getSyncHttpClient()); builder.region(Region.of("us-east-1")); builder.booleanContextParam(true); builder.stringContextParam("this is a test"); APostOperationRequest request = APostOperationRequest.builder().build(); builder.build().aPostOperation(request); - }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://myservice.aws")).build()).build()), + }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://foo-myservice.aws")).build()).build()), new SyncTestCase("test case 3", () -> { QueryClientBuilder builder = QueryClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getSyncHttpClient()); builder.region(Region.of("us-east-1")); OperationWithContextParamRequest request = OperationWithContextParamRequest.builder() @@ -77,6 +80,7 @@ private static List syncTestCases() { new SyncTestCase("test case 4", () -> { QueryClientBuilder builder = QueryClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getSyncHttpClient()); builder.region(Region.of("us-east-6")); OperationWithContextParamRequest request = OperationWithContextParamRequest.builder() @@ -87,6 +91,7 @@ private static List syncTestCases() { new SyncTestCase("For region us-iso-west-1 with FIPS enabled and DualStack enabled", () -> { QueryClientBuilder builder = QueryClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getSyncHttpClient()); APostOperationRequest request = APostOperationRequest.builder().build(); builder.build().aPostOperation(request); @@ -94,6 +99,7 @@ private static List syncTestCases() { new SyncTestCase("Has complex operation input", () -> { QueryClientBuilder builder = QueryClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getSyncHttpClient()); OperationWithContextParamRequest request = OperationWithContextParamRequest.builder() .nestedMember(ChecksumStructure.builder().checksumMode("foo").build()).build(); @@ -106,24 +112,27 @@ private static List asyncTestCases() { new AsyncTestCase("test case 1", () -> { QueryAsyncClientBuilder builder = QueryAsyncClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getAsyncHttpClient()); builder.region(Region.of("us-east-1")); APostOperationRequest request = APostOperationRequest.builder().build(); return builder.build().aPostOperation(request); - }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://myservice.aws")).build()).build()), + }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://foo-myservice.aws")).build()).build()), new AsyncTestCase("test case 2", () -> { QueryAsyncClientBuilder builder = QueryAsyncClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getAsyncHttpClient()); builder.region(Region.of("us-east-1")); builder.booleanContextParam(true); builder.stringContextParam("this is a test"); APostOperationRequest request = APostOperationRequest.builder().build(); return builder.build().aPostOperation(request); - }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://myservice.aws")).build()).build()), + }, Expect.builder().endpoint(Endpoint.builder().url(URI.create("https://foo-myservice.aws")).build()).build()), new AsyncTestCase("test case 3", () -> { QueryAsyncClientBuilder builder = QueryAsyncClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getAsyncHttpClient()); builder.region(Region.of("us-east-1")); OperationWithContextParamRequest request = OperationWithContextParamRequest.builder() @@ -133,6 +142,7 @@ private static List asyncTestCases() { new AsyncTestCase("test case 4", () -> { QueryAsyncClientBuilder builder = QueryAsyncClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getAsyncHttpClient()); builder.region(Region.of("us-east-6")); OperationWithContextParamRequest request = OperationWithContextParamRequest.builder() @@ -143,6 +153,7 @@ private static List asyncTestCases() { new AsyncTestCase("For region us-iso-west-1 with FIPS enabled and DualStack enabled", () -> { QueryAsyncClientBuilder builder = QueryAsyncClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getAsyncHttpClient()); APostOperationRequest request = APostOperationRequest.builder().build(); return builder.build().aPostOperation(request); @@ -150,6 +161,7 @@ private static List asyncTestCases() { new AsyncTestCase("Has complex operation input", () -> { QueryAsyncClientBuilder builder = QueryAsyncClient.builder(); builder.credentialsProvider(BaseRuleSetClientTest.CREDENTIALS_PROVIDER); + builder.tokenProvider(BaseRuleSetClientTest.TOKEN_PROVIDER); builder.httpClient(getAsyncHttpClient()); OperationWithContextParamRequest request = OperationWithContextParamRequest.builder() .nestedMember(ChecksumStructure.builder().checksumMode("foo").build()).build(); diff --git a/core/endpoints-spi/pom.xml b/core/endpoints-spi/pom.xml index 289da3659fd4..e4c75daed667 100644 --- a/core/endpoints-spi/pom.xml +++ b/core/endpoints-spi/pom.xml @@ -48,5 +48,20 @@ annotations ${awsjavasdk.version} + + org.junit.jupiter + junit-jupiter + test + + + nl.jqno.equalsverifier + equalsverifier + test + + + org.assertj + assertj-core + test + diff --git a/core/endpoints-spi/src/main/java/software/amazon/awssdk/endpoints/Endpoint.java b/core/endpoints-spi/src/main/java/software/amazon/awssdk/endpoints/Endpoint.java index 4d2ed7ee31da..7db593ba3376 100644 --- a/core/endpoints-spi/src/main/java/software/amazon/awssdk/endpoints/Endpoint.java +++ b/core/endpoints-spi/src/main/java/software/amazon/awssdk/endpoints/Endpoint.java @@ -46,11 +46,43 @@ public Map> headers() { return headers; } + public Builder toBuilder() { + return new BuilderImpl(this); + } + @SuppressWarnings("unchecked") public T attribute(EndpointAttributeKey key) { return (T) attributes.get(key); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Endpoint endpoint = (Endpoint) o; + + if (url != null ? !url.equals(endpoint.url) : endpoint.url != null) { + return false; + } + if (headers != null ? !headers.equals(endpoint.headers) : endpoint.headers != null) { + return false; + } + return attributes != null ? attributes.equals(endpoint.attributes) : endpoint.attributes == null; + } + + @Override + public int hashCode() { + int result = url != null ? url.hashCode() : 0; + result = 31 * result + (headers != null ? headers.hashCode() : 0); + result = 31 * result + (attributes != null ? attributes.hashCode() : 0); + return result; + } + public static Builder builder() { return new BuilderImpl(); } @@ -70,6 +102,19 @@ private static class BuilderImpl implements Builder { private final Map> headers = new HashMap<>(); private final Map, Object> attributes = new HashMap<>(); + private BuilderImpl() { + } + + private BuilderImpl(Endpoint e) { + this.url = e.url; + if (e.headers != null) { + e.headers.forEach((n, v) -> { + this.headers.put(n, new ArrayList<>(v)); + }); + } + this.attributes.putAll(e.attributes); + } + @Override public Builder url(URI url) { this.url = url; diff --git a/core/endpoints-spi/src/test/java/software/amazon/awssdk/endpoints/EndpointTest.java b/core/endpoints-spi/src/test/java/software/amazon/awssdk/endpoints/EndpointTest.java new file mode 100644 index 000000000000..e6e3cb2a8983 --- /dev/null +++ b/core/endpoints-spi/src/test/java/software/amazon/awssdk/endpoints/EndpointTest.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.endpoints; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +public class EndpointTest { + private static final EndpointAttributeKey TEST_STRING_ATTR = + new EndpointAttributeKey<>("StringAttr", String.class); + + @Test + public void testEqualsHashCode() { + EqualsVerifier.forClass(Endpoint.class) + .verify(); + } + + @Test + public void build_maximal() { + Endpoint endpoint = Endpoint.builder() + .url(URI.create("https://myservice.aws")) + .putHeader("foo", "bar") + .putHeader("foo", "baz") + .putAttribute(TEST_STRING_ATTR, "baz") + .build(); + + Map> expectedHeaders = new HashMap<>(); + expectedHeaders.put("foo", Arrays.asList("bar", "baz")); + + assertThat(endpoint.url()).isEqualTo(URI.create("https://myservice.aws")); + assertThat(endpoint.headers()).isEqualTo(expectedHeaders); + assertThat(endpoint.attribute(TEST_STRING_ATTR)).isEqualTo("baz"); + } + + @Test + public void toBuilder_unmodified_equalToOriginal() { + Endpoint original = Endpoint.builder() + .url(URI.create("https://myservice.aws")) + .putHeader("foo", "bar") + .putAttribute(TEST_STRING_ATTR, "baz") + .build(); + + assertThat(original.toBuilder().build()).isEqualTo(original); + } + + @Test + public void toBuilder_headersModified_notReflectedInOriginal() { + Endpoint original = Endpoint.builder() + .putHeader("foo", "bar") + .build(); + + original.toBuilder() + .putHeader("foo", "baz") + .build(); + + assertThat(original.headers().get("foo")).containsExactly("bar"); + } + + @Test + public void toBuilder_attrsModified_notReflectedInOriginal() { + Endpoint original = Endpoint.builder() + .putAttribute(TEST_STRING_ATTR, "foo") + .build(); + + original.toBuilder() + .putAttribute(TEST_STRING_ATTR, "bar") + .build(); + + assertThat(original.attribute(TEST_STRING_ATTR)).isEqualTo("foo"); + } +} diff --git a/services/eventbridge/pom.xml b/services/eventbridge/pom.xml index 1ddb32ea50b7..f0ccf15a2b72 100644 --- a/services/eventbridge/pom.xml +++ b/services/eventbridge/pom.xml @@ -56,16 +56,6 @@ aws-json-protocol ${awsjavasdk.version} - - software.amazon.awssdk - json-utils - ${awsjavasdk.version} - - - software.amazon.awssdk - endpoints-spi - ${awsjavasdk.version} - software.amazon.awssdk diff --git a/services/eventbridge/src/main/resources/codegen-resources/customization.config b/services/eventbridge/src/main/resources/codegen-resources/customization.config index 66e541ef8380..045f4d5b3184 100644 --- a/services/eventbridge/src/main/resources/codegen-resources/customization.config +++ b/services/eventbridge/src/main/resources/codegen-resources/customization.config @@ -6,6 +6,5 @@ "Invalid EndpointId (empty)": "Need operationInputs for EndpointId param", "Valid endpointId with fips disabled and dualstack true": "Need operationInputs for EndpointId param", "Valid endpointId with custom sdk endpoint": "Need operationInputs for EndpointId param" - }, - "useRuleBasedEndpoints": true -} \ No newline at end of file + } +} diff --git a/services/pom.xml b/services/pom.xml index 546dc69deb59..4a14163eacf1 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -408,6 +408,16 @@ metrics-spi ${awsjavasdk.version} + + software.amazon.awssdk + json-utils + ${awsjavasdk.version} + + + software.amazon.awssdk + endpoints-spi + ${awsjavasdk.version} + apache-client software.amazon.awssdk diff --git a/services/s3/pom.xml b/services/s3/pom.xml index 5a54946700bd..68ad2fc96cc0 100644 --- a/services/s3/pom.xml +++ b/services/s3/pom.xml @@ -75,16 +75,6 @@ profiles ${awsjavasdk.version} - - software.amazon.awssdk - json-utils - ${awsjavasdk.version} - - - software.amazon.awssdk - endpoints-spi - ${awsjavasdk.version} - software.amazon.awssdk.crt aws-crt diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index cd3b26b7e1cc..78df003f035c 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -236,7 +236,6 @@ }, "delegateAsyncClientClass": true, "useGlobalEndpoint": true, - "useRuleBasedEndpoints": true, "interceptors": [ "software.amazon.awssdk.services.s3.internal.handlers.PutObjectInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.CreateBucketInterceptor", diff --git a/services/s3control/pom.xml b/services/s3control/pom.xml index 21ae8550216e..522f7cbac1c4 100644 --- a/services/s3control/pom.xml +++ b/services/s3control/pom.xml @@ -71,16 +71,6 @@ profiles ${awsjavasdk.version} - - software.amazon.awssdk - json-utils - ${awsjavasdk.version} - - - software.amazon.awssdk - endpoints-spi - ${awsjavasdk.version} - commons-io diff --git a/services/s3control/src/main/resources/codegen-resources/customization.config b/services/s3control/src/main/resources/codegen-resources/customization.config index 7027f5ac1203..9248a2ded08c 100644 --- a/services/s3control/src/main/resources/codegen-resources/customization.config +++ b/services/s3control/src/main/resources/codegen-resources/customization.config @@ -54,7 +54,6 @@ "Bucket ARN with region mismatch and UseArnRegion unset": "SDK defaults to useArnRegion = false", "Accesspoint ARN with region mismatch, UseArnRegion=false and custom endpoint": "Does not work for client tests because operationInputs needed (so an operation that doesn't required AccountId is used)" }, - "useRuleBasedEndpoints": true, "interceptors": [ "software.amazon.awssdk.services.s3control.internal.interceptors.ConfigureSignerInterceptor", "software.amazon.awssdk.services.s3control.internal.interceptors.PayloadSigningInterceptor" diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/customization.config b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/customization.config index 789ac307ef1d..ec7c8d355d50 100644 --- a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/customization.config +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/customization.config @@ -1,4 +1,3 @@ { - "skipEndpointTestGeneration": true, - "useRuleBasedEndpoints": true -} \ No newline at end of file + "skipEndpointTestGeneration": true +} diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/service-2.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/service-2.json index 0a652d29a7f5..c24513d13546 100644 --- a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/service-2.json +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointproviders/service-2.json @@ -23,6 +23,16 @@ } }, "operations":{ + "OperationWithHostPrefix": { + "name": "OperationWithStaticContextParamA", + "http": { + "method": "POST", + "requestUri": "/" + }, + "endpoint": { + "hostPrefix": "foo." + } + }, "OperationWithStaticContextParamA": { "name": "OperationWithStaticContextParamA", "http": { diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java index 8ef4997281e8..909543b2a601 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java @@ -85,6 +85,26 @@ public void endpointIsDiscovered_attrIsTrue_returnsTrue() { assertThat(AwsEndpointProviderUtils.endpointIsDiscovered(attrs)).isTrue(); } + @Test + public void disableHostPrefixInjection_attrIsFalse_returnsFalse() { + ExecutionAttributes attrs = new ExecutionAttributes(); + attrs.putAttribute(SdkInternalExecutionAttribute.DISABLE_HOST_PREFIX_INJECTION, false); + assertThat(AwsEndpointProviderUtils.disableHostPrefixInjection(attrs)).isFalse(); + } + + @Test + public void disableHostPrefixInjection_attrIsAbsent_returnsFalse() { + ExecutionAttributes attrs = new ExecutionAttributes(); + assertThat(AwsEndpointProviderUtils.disableHostPrefixInjection(attrs)).isFalse(); + } + + @Test + public void disableHostPrefixInjection_attrIsTrue_returnsTrue() { + ExecutionAttributes attrs = new ExecutionAttributes(); + attrs.putAttribute(SdkInternalExecutionAttribute.DISABLE_HOST_PREFIX_INJECTION, true); + assertThat(AwsEndpointProviderUtils.disableHostPrefixInjection(attrs)).isTrue(); + } + @Test public void valueAsEndpoint_isNone_throws() { assertThatThrownBy(() -> AwsEndpointProviderUtils.valueAsEndpointOrThrow(Value.none())) @@ -270,4 +290,58 @@ public void setHeaders_noExistingOverrideConfig_createsOverrideConfig() { assertThat(newRequest.overrideConfiguration().get().headers()).isEqualTo(expectedHeaders); } + + @Test + public void addHostPrefix_prefixIsNull_returnsUnModified() { + URI url = URI.create("https://foo.aws"); + Endpoint e = Endpoint.builder() + .url(url) + .build(); + + assertThat(AwsEndpointProviderUtils.addHostPrefix(e, null).url()).isEqualTo(url); + } + + @Test + public void addHostPrefix_prefixIsEmpty_returnsUnModified() { + URI url = URI.create("https://foo.aws"); + Endpoint e = Endpoint.builder() + .url(url) + .build(); + + assertThat(AwsEndpointProviderUtils.addHostPrefix(e, "").url()).isEqualTo(url); + } + + @Test + public void addHostPrefix_prefixPresent_returnsPrefixPrepended() { + URI url = URI.create("https://foo.aws"); + Endpoint e = Endpoint.builder() + .url(url) + .build(); + + URI expected = URI.create("https://api.foo.aws"); + assertThat(AwsEndpointProviderUtils.addHostPrefix(e, "api.").url()).isEqualTo(expected); + } + + @Test + public void addHostPrefix_prefixPresent_preservesPortPathAndQuery() { + URI url = URI.create("https://foo.aws:1234/a/b/c?queryParam1=val1"); + Endpoint e = Endpoint.builder() + .url(url) + .build(); + + URI expected = URI.create("https://api.foo.aws:1234/a/b/c?queryParam1=val1"); + assertThat(AwsEndpointProviderUtils.addHostPrefix(e, "api.").url()).isEqualTo(expected); + } + + @Test + public void addHostPrefix_prefixInvalid_throws() { + URI url = URI.create("https://foo.aws"); + Endpoint e = Endpoint.builder() + .url(url) + .build(); + + assertThatThrownBy(() -> AwsEndpointProviderUtils.addHostPrefix(e, "foo#bar.*baz")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("component must match the pattern"); + } } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java index 05d6c9245ab8..e9e10177cc8d 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java @@ -21,6 +21,7 @@ import org.junit.Test; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; @@ -35,6 +36,38 @@ public class EndpointInterceptorTests { + @Test + public void sync_hostPrefixInjectDisabled_hostPrefixNotAdded() { + CapturingInterceptor interceptor = new CapturingInterceptor(); + RestJsonEndpointProvidersClient client = syncClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(interceptor) + .putAdvancedOption(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION, true)) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {})) + .hasMessageContaining("stop"); + + Endpoint endpoint = interceptor.executionAttributes().getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT); + + assertThat(endpoint.url().getHost()).isEqualTo("restjson.us-west-2.amazonaws.com"); + } + + @Test + public void async_hostPrefixInjectDisabled_hostPrefixNotAdded() { + CapturingInterceptor interceptor = new CapturingInterceptor(); + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(interceptor) + .putAdvancedOption(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION, true)) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + Endpoint endpoint = interceptor.executionAttributes().getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT); + + assertThat(endpoint.url().getHost()).isEqualTo("restjson.us-west-2.amazonaws.com"); + } + @Test public void sync_clientContextParamsSetOnBuilder_includedInExecutionAttributes() { CapturingInterceptor interceptor = new CapturingInterceptor(); diff --git a/test/protocol-tests/pom.xml b/test/protocol-tests/pom.xml index a7c88143e860..66967ddba7dd 100644 --- a/test/protocol-tests/pom.xml +++ b/test/protocol-tests/pom.xml @@ -108,6 +108,16 @@ utils ${awsjavasdk.version} + + software.amazon.awssdk + json-utils + ${awsjavasdk.version} + + + software.amazon.awssdk + endpoints-spi + ${awsjavasdk.version} + diff --git a/test/ruleset-testing-core/src/main/java/software/amazon/awssdk/core/rules/testing/BaseRuleSetClientTest.java b/test/ruleset-testing-core/src/main/java/software/amazon/awssdk/core/rules/testing/BaseRuleSetClientTest.java index 6a2514828935..4beb82e73fa5 100644 --- a/test/ruleset-testing-core/src/main/java/software/amazon/awssdk/core/rules/testing/BaseRuleSetClientTest.java +++ b/test/ruleset-testing-core/src/main/java/software/amazon/awssdk/core/rules/testing/BaseRuleSetClientTest.java @@ -24,6 +24,8 @@ import static org.mockito.Mockito.when; import java.net.URI; +import java.time.Instant; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.junit.jupiter.api.Assumptions; @@ -33,6 +35,9 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.auth.token.credentials.SdkToken; +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; +import software.amazon.awssdk.auth.token.credentials.StaticTokenProvider; import software.amazon.awssdk.core.rules.testing.model.Expect; import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.HttpExecuteRequest; @@ -45,6 +50,7 @@ public abstract class BaseRuleSetClientTest { protected static final AwsCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + protected static final SdkTokenProvider TOKEN_PROVIDER = StaticTokenProvider.create(new TestSdkToken()); private static SdkHttpClient syncHttpClient; private static SdkAsyncHttpClient asyncHttpClient; @@ -122,4 +128,17 @@ protected static SdkHttpClient getSyncHttpClient() { protected static SdkAsyncHttpClient getAsyncHttpClient() { return asyncHttpClient; } + + private static class TestSdkToken implements SdkToken { + + @Override + public String token() { + return "TOKEN"; + } + + @Override + public Optional expirationTime() { + return Optional.empty(); + } + } }