diff --git a/.changes/next-release/feature-AWSSDKforJavav2-ee91960.json b/.changes/next-release/feature-AWSSDKforJavav2-ee91960.json new file mode 100644 index 000000000000..cabc42ee1bd8 --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-ee91960.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Add business metrics support for RPC v2 CBOR protocol to track smithy rpcv2 cbor protocol usage." +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-rpcv2-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-rpcv2-async-client-class.java index 547fe51ec475..bd3083b4602b 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-rpcv2-async-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-rpcv2-async-client-class.java @@ -1170,4 +1170,4 @@ private HttpResponseHandler createErrorResponseHandler(Base public void close() { clientHandler.close(); } -} +} \ No newline at end of file diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java index 4a71ba7681fb..067e48926990 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.awscore.internal; import static software.amazon.awssdk.auth.signer.internal.util.SignerMethodResolver.resolveSigningMethodUsed; +import static software.amazon.awssdk.awscore.internal.AwsServiceProtocol.SMITHY_RPC_V2_CBOR; import static software.amazon.awssdk.core.client.config.SdkClientOption.RETRY_POLICY; import static software.amazon.awssdk.core.client.config.SdkClientOption.RETRY_STRATEGY; import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS; @@ -35,6 +36,7 @@ import software.amazon.awssdk.awscore.util.SignerOverrideUtils; import software.amazon.awssdk.core.HttpChecksumConstant; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkProtocolMetadata; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.SelectedAuthScheme; @@ -56,6 +58,7 @@ import software.amazon.awssdk.core.sync.ResponseTransformer; import software.amazon.awssdk.core.useragent.AdditionalMetadata; import software.amazon.awssdk.core.useragent.BusinessMetricCollection; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.endpoints.EndpointProvider; import software.amazon.awssdk.http.ContentStreamProvider; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; @@ -133,7 +136,8 @@ private AwsExecutionContextBuilder() { clientConfig.option(SdkClientOption.REQUEST_CHECKSUM_CALCULATION)) .putAttribute(SdkInternalExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION, clientConfig.option(SdkClientOption.RESPONSE_CHECKSUM_VALIDATION)) - .putAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS, resolveUserAgentBusinessMetrics(clientConfig)) + .putAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS, + resolveUserAgentBusinessMetrics(clientConfig, executionParams)) .putAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET, clientConfig.option(AwsClientOption.AWS_SIGV4A_SIGNING_REGION_SET)); @@ -350,11 +354,23 @@ private static EndpointProvider resolveEndpointProvider(SdkRequest request, .orElse(clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER)); } - private static BusinessMetricCollection resolveUserAgentBusinessMetrics(SdkClientConfiguration clientConfig) { + private static BusinessMetricCollection + resolveUserAgentBusinessMetrics(SdkClientConfiguration clientConfig, + ClientExecutionParams executionParams) { BusinessMetricCollection businessMetrics = new BusinessMetricCollection(); Optional retryModeMetric = resolveRetryMode(clientConfig.option(RETRY_POLICY), clientConfig.option(RETRY_STRATEGY)); retryModeMetric.ifPresent(businessMetrics::addMetric); + + if (isRpcV2CborProtocol(executionParams.getProtocolMetadata())) { + businessMetrics.addMetric(BusinessMetricFeatureId.PROTOCOL_RPC_V2_CBOR.value()); + } + return businessMetrics; } + + private static boolean isRpcV2CborProtocol(SdkProtocolMetadata protocolMetadata) { + return protocolMetadata != null && + SMITHY_RPC_V2_CBOR.toString().equals(protocolMetadata.serviceProtocol()); + } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java index 884f57bf5691..df481f975d50 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java @@ -22,7 +22,7 @@ /** * An enum class representing a short form of identity providers to record in the UA string. * - * Unimplemented metrics: I,J,K,M,O,S,U-c + * Unimplemented metrics: I,J,K,O,S,U-c * Unsupported metrics (these will never be added): A,H */ @SdkProtectedApi @@ -35,6 +35,7 @@ public enum BusinessMetricFeatureId { RETRY_MODE_ADAPTIVE("F"), S3_TRANSFER("G"), GZIP_REQUEST_COMPRESSION("L"), //TODO(metrics): Not working, compression happens after header + PROTOCOL_RPC_V2_CBOR("M"), ENDPOINT_OVERRIDE("N"), ACCOUNT_ID_MODE_PREFERRED("P"), ACCOUNT_ID_MODE_DISABLED("Q"), diff --git a/test/codegen-generated-classes-test/pom.xml b/test/codegen-generated-classes-test/pom.xml index d1c4ac3f63e6..a7717e5b02a3 100644 --- a/test/codegen-generated-classes-test/pom.xml +++ b/test/codegen-generated-classes-test/pom.xml @@ -145,6 +145,11 @@ retries-spi ${awsjavasdk.version} + + software.amazon.awssdk + smithy-rpcv2-protocol + ${awsjavasdk.version} + netty-nio-client software.amazon.awssdk diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/customization.config b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/customization.config new file mode 100644 index 000000000000..4a57cda840dd --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/customization.config @@ -0,0 +1,4 @@ +{ + "enableGenerateCompiledEndpointRules": true, + "skipEndpointTestGeneration": true +} diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/endpoint-rule-set.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/endpoint-rule-set.json new file mode 100644 index 000000000000..8c3cc3052beb --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/endpoint-rule-set.json @@ -0,0 +1,30 @@ +{ + "version": "1.3", + "parameters": { + "Region": { + "builtIn": "AWS::Region", + "required": true, + "documentation": "The AWS region used to dispatch the request.", + "type": "String" + } + }, + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "http://localhost/", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "jsonrpc" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] +} diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/endpoint-tests.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/endpoint-tests.json new file mode 100644 index 000000000000..f94902ff9d99 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/endpoint-tests.json @@ -0,0 +1,5 @@ +{ + "testCases": [ + ], + "version": "1.0" +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/service-2.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/service-2.json new file mode 100644 index 000000000000..4d3c2ea4a0d7 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sdkrpcv2/service-2.json @@ -0,0 +1,724 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2016-03-11", + "endpointPrefix": "smithyrpcv2", + "jsonVersion": "1.1", + "protocol": "smithy-rpc-v2-cbor", + "protocols": [ + "smithy-rpc-v2-cbor" + ], + "serviceAbbreviation": "SmithyRpcV2ProtocolTests", + "serviceFullName": "Smithy RPCv2 Protocol Tests", + "serviceId": "ProtocolSmithyrpcv2", + "signatureVersion": "v4", + "targetPrefix": "ProtocolTestsSmithyRpcV2Service", + "uid": "smithyrpcv2-2016-03-11" + }, + "operations": { + "GetMetricData": { + "name": "GetMetricData", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "GetMetricDataRequest" + }, + "output": { + "shape": "GetMetricDataResponse" + } + }, + "AllTypes": { + "name": "AllTypes", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "AllTypesStructure" + }, + "output": { + "shape": "AllTypesStructure" + }, + "errors": [ + { + "shape": "EmptyModeledException" + }, + { + "shape": "ImplicitPayloadException" + } + ] + }, + "FurtherNestedContainers": { + "name": "FurtherNestedContainers", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "FurtherNestedContainersStructure" + }, + "output": { + "shape": "FurtherNestedContainersStructure" + } + }, + "IdempotentOperation": { + "name": "IdempotentOperation", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "IdempotentOperationStructure" + }, + "output": { + "shape": "IdempotentOperationStructure" + } + }, + "NestedContainers": { + "name": "NestedContainers", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "NestedContainersStructure" + }, + "output": { + "shape": "NestedContainersStructure" + } + }, + "OperationWithNoInputOrOutput": { + "name": "OperationWithNoInputOrOutput", + "http": { + "method": "POST", + "requestUri": "/" + } + }, + "GreetingWithErrors":{ + "name":"GreetingWithErrors", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "output":{"shape":"GreetingWithErrorsOutput"}, + "errors":[ + {"shape":"ComplexError"}, + {"shape":"InvalidGreeting"} + ], + "idempotent":true + } + }, + "shapes": { + "GetMetricDataRequest": { + "type": "structure", + "members": { + "id": { + "shape": "String" + } + } + }, + "GetMetricDataResponse": { + "type": "structure", + "members": { + "id": { + "shape": "String" + }, + "label": { + "shape": "String" + }, + "statusCode": { + "shape": "StatusCode" + }, + "timestamps": { + "shape": "ListOfTimeStamp" + }, + "values": { + "shape": "ListOfDouble" + } + } + }, + "ListOfDouble": { + "type": "list", + "member": { + "shape": "Double" + } + }, + "StatusCode": { + "type": "string", + "enum": [ + "Complete", + "InProgess" + ] + }, + "AllTypesStructure": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + }, + "IntegerMember": { + "shape": "Integer" + }, + "BooleanMember": { + "shape": "Boolean" + }, + "FloatMember": { + "shape": "Float" + }, + "DoubleMember": { + "shape": "Double" + }, + "BigDecimalMember": { + "shape": "NumericValue" + }, + "LongMember": { + "shape": "Long" + }, + "ShortMember": { + "shape": "Short" + }, + "ByteMember": { + "shape": "Byte" + }, + "SimpleList": { + "shape": "ListOfStrings" + }, + "ListOfMaps": { + "shape": "ListOfMapStringToString" + }, + "ListOfStructs": { + "shape": "ListOfSimpleStructs" + }, + "MapOfStringToIntegerList": { + "shape": "MapOfStringToIntegerList" + }, + "MapOfStringToString": { + "shape": "MapOfStringToString" + }, + "MapOfStringToStruct": { + "shape": "MapOfStringToSimpleStruct" + }, + "TimestampMember": { + "shape": "Timestamp" + }, + "StructWithNestedTimestampMember": { + "shape": "StructWithTimestamp" + }, + "TimestampFormatMember": { + "shape": "IsoTimestamp" + }, + "BlobArg": { + "shape": "BlobType" + }, + "StructWithNestedBlob": { + "shape": "StructWithNestedBlobType" + }, + "BlobMap": { + "shape": "BlobMapType" + }, + "ListOfBlobs": { + "shape": "ListOfBlobsType" + }, + "RecursiveStruct": { + "shape": "RecursiveStructType" + }, + "PolymorphicTypeWithSubTypes": { + "shape": "BaseType" + }, + "PolymorphicTypeWithoutSubTypes": { + "shape": "SubTypeOne" + }, + "EnumMember": { + "shape": "EnumType" + }, + "ListOfEnums": { + "shape": "ListOfEnums" + }, + "MapOfEnumToEnum": { + "shape": "MapOfEnumToEnum" + }, + "ListOfTimeStamp": { + "shape": "ListOfTimeStamp" + }, + "MapOfTimeStamp": { + "shape": "MapOfTimeStamp" + }, + "MyDocument": { + "shape": "MyDocument" + }, + "UnionMember": { + "shape": "AllTypesUnionStructure" + } + } + }, + "BaseType": { + "type": "structure", + "members": { + "BaseMember": { + "shape": "String" + } + } + }, + "BlobMapType": { + "type": "map", + "key": { + "shape": "String" + }, + "value": { + "shape": "BlobType" + } + }, + "BlobType": { + "type": "blob" + }, + "Boolean": { + "type": "boolean" + }, + "Double": { + "type": "double" + }, + "EmptyModeledException": { + "type": "structure", + "members": {}, + "exception": true + }, + "EnumType": { + "type": "string", + "enum": [ + "EnumValue1", + "EnumValue2" + ] + }, + "Float": { + "type": "float" + }, + "Short": { + "type": "short" + }, + "Byte": { + "type": "byte" + }, + "FurtherNestedContainersStructure": { + "type": "structure", + "members": { + "ListOfNested": { + "shape": "ListOfNested" + } + } + }, + "IdempotentOperationStructure": { + "type": "structure", + "members": { + "IdempotencyToken": { + "shape": "String", + "idempotencyToken": true + } + } + }, + "ImplicitPayloadException": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + }, + "IntegerMember": { + "shape": "Integer" + }, + "LongMember": { + "shape": "Long" + }, + "ShortMember": { + "shape": "Short" + }, + "DoubleMember": { + "shape": "Double" + }, + "FloatMember": { + "shape": "Float" + }, + "TimestampMember": { + "shape": "Timestamp" + }, + "BooleanMember": { + "shape": "Boolean" + }, + "BlobMember": { + "shape": "BlobType" + }, + "ListMember": { + "shape": "ListOfStrings" + }, + "MapMember": { + "shape": "MapOfStringToString" + }, + "SimpleStructMember": { + "shape": "SimpleStruct" + } + }, + "exception": true + }, + "Integer": { + "type": "integer" + }, + "NumericValue": { + "type": "string", + "pattern": "([0-9]*\\.)?[0-9]+" + }, + "IsoTimestamp": { + "type": "timestamp", + "timestampFormat": "iso8601" + }, + "UnixTimestamp": { + "type": "timestamp", + "timestampFormat": "unixTimestamp" + }, + "ListOfAllTypesStructs": { + "type": "list", + "member": { + "shape": "AllTypesStructure" + } + }, + "ListOfBlobsType": { + "type": "list", + "member": { + "shape": "BlobType" + } + }, + "ListOfEnums": { + "type": "list", + "member": { + "shape": "EnumType" + } + }, + "ListOfIntegers": { + "type": "list", + "member": { + "shape": "Integer" + } + }, + "ListOfListOfListsOfStrings": { + "type": "list", + "member": { + "shape": "ListOfListsOfStrings" + } + }, + "ListOfListsOfAllTypesStructs": { + "type": "list", + "member": { + "shape": "ListOfAllTypesStructs" + } + }, + "ListOfListsOfStrings": { + "type": "list", + "member": { + "shape": "ListOfStrings" + } + }, + "ListOfListsOfStructs": { + "type": "list", + "member": { + "shape": "ListOfSimpleStructs" + } + }, + "ListOfMapStringToString": { + "type": "list", + "member": { + "shape": "MapOfStringToString" + } + }, + "ListOfNested": { + "type": "list", + "member": { + "shape": "NestedContainersStructure" + } + }, + "ListOfSimpleStructs": { + "type": "list", + "member": { + "shape": "SimpleStruct" + } + }, + "ListOfStrings": { + "type": "list", + "member": { + "shape": "String" + } + }, + "Long": { + "type": "long" + }, + "MapOfEnumToEnum": { + "type": "map", + "key": { + "shape": "EnumType" + }, + "value": { + "shape": "EnumType" + } + }, + "MapOfStringToIntegerList": { + "type": "map", + "key": { + "shape": "String" + }, + "value": { + "shape": "ListOfIntegers" + } + }, + "MapOfStringToListOfListsOfStrings": { + "type": "map", + "key": { + "shape": "String" + }, + "value": { + "shape": "ListOfListsOfStrings" + } + }, + "MapOfStringToSimpleStruct": { + "type": "map", + "key": { + "shape": "String" + }, + "value": { + "shape": "SimpleStruct" + } + }, + "MapOfStringToString": { + "type": "map", + "key": { + "shape": "String" + }, + "value": { + "shape": "String" + } + }, + "NestedContainersStructure": { + "type": "structure", + "members": { + "ListOfListsOfStrings": { + "shape": "ListOfListsOfStrings" + }, + "ListOfListsOfStructs": { + "shape": "ListOfListsOfStructs" + }, + "ListOfListsOfAllTypesStructs": { + "shape": "ListOfListsOfAllTypesStructs" + }, + "ListOfListOfListsOfStrings": { + "shape": "ListOfListOfListsOfStrings" + }, + "MapOfStringToListOfListsOfStrings": { + "shape": "MapOfStringToListOfListsOfStrings" + }, + "StringMember": { + "shape": "String" + } + } + }, + "RecursiveListType": { + "type": "list", + "member": { + "shape": "RecursiveStructType" + } + }, + "RecursiveMapType": { + "type": "map", + "key": { + "shape": "String" + }, + "value": { + "shape": "RecursiveStructType" + } + }, + "RecursiveStructType": { + "type": "structure", + "members": { + "NoRecurse": { + "shape": "String" + }, + "RecursiveStruct": { + "shape": "RecursiveStructType" + }, + "RecursiveList": { + "shape": "RecursiveListType" + }, + "RecursiveMap": { + "shape": "RecursiveMapType" + } + } + }, + "SimpleStruct": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + } + } + }, + "String": { + "type": "string" + }, + "StructWithNestedBlobType": { + "type": "structure", + "members": { + "NestedBlob": { + "shape": "BlobType" + } + } + }, + "StructWithTimestamp": { + "type": "structure", + "members": { + "NestedTimestamp": { + "shape": "Timestamp" + } + } + }, + "SubTypeOne": { + "type": "structure", + "members": { + "SubTypeOneMember": { + "shape": "String" + } + } + }, + "Timestamp": { + "type": "timestamp" + }, + "ListOfTimeStamp": { + "type": "list", + "member": { + "shape": "UnixTimestamp" + } + }, + "MapOfTimeStamp": { + "type": "map", + "key": { + "shape": "String" + }, + "value": { + "shape": "UnixTimestamp" + } + }, + "MyDocument": { + "type": "structure", + "document": true + }, + "AllTypesUnionStructure": { + "type": "structure", + "union": true, + "members": { + "StringMember": { + "shape": "String" + }, + "IntegerMember": { + "shape": "Integer" + }, + "BooleanMember": { + "shape": "Boolean" + }, + "FloatMember": { + "shape": "Float" + }, + "DoubleMember": { + "shape": "Double" + }, + "LongMember": { + "shape": "Long" + }, + "ShortMember": { + "shape": "Short" + }, + "EnumMember": { + "shape": "EnumType" + }, + "SimpleList": { + "shape": "ListOfStrings" + }, + "ListOfEnums": { + "shape": "ListOfEnums" + }, + "ListOfMaps": { + "shape": "ListOfMapStringToString" + }, + "ListOfStructs": { + "shape": "ListOfSimpleStructs" + }, + "MapOfStringToIntegerList": { + "shape": "MapOfStringToIntegerList" + }, + "MapOfStringToString": { + "shape": "MapOfStringToString" + }, + "MapOfStringToStruct": { + "shape": "MapOfStringToSimpleStruct" + }, + "MapOfEnumToEnum": { + "shape": "MapOfEnumToEnum" + }, + "TimestampMember": { + "shape": "Timestamp" + }, + "StructWithNestedTimestampMember": { + "shape": "StructWithTimestamp" + }, + "BlobArg": { + "shape": "BlobType" + }, + "StructWithNestedBlob": { + "shape": "StructWithNestedBlobType" + }, + "BlobMap": { + "shape": "BlobMapType" + }, + "ListOfBlobs": { + "shape": "ListOfBlobsType" + }, + "RecursiveStruct": { + "shape": "RecursiveStructType" + }, + "PolymorphicTypeWithSubTypes": { + "shape": "BaseType" + }, + "PolymorphicTypeWithoutSubTypes": { + "shape": "SubTypeOne" + }, + "SetPrefixedMember": { + "shape": "String" + }, + "UnionMember": { + "shape": "AllTypesUnionStructure" + } + } + }, + "GreetingWithErrorsOutput":{ + "type":"structure", + "members":{ + "greeting":{"shape":"String"} + } + }, + "ComplexError":{ + "type":"structure", + "members":{ + "TopLevel":{"shape":"String"}, + "Nested":{"shape":"ComplexNestedErrorData"} + }, + "exception":true + }, + "ComplexNestedErrorData":{ + "type":"structure", + "members":{ + "Foo":{"shape":"String"} + } + }, + "InvalidGreeting":{ + "type":"structure", + "members":{ + "Message":{"shape":"String"} + }, + "exception":true + } + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/rpcv2cbor/RpcV2CborUserAgentTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/rpcv2cbor/RpcV2CborUserAgentTest.java new file mode 100644 index 000000000000..070158445a35 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/rpcv2cbor/RpcV2CborUserAgentTest.java @@ -0,0 +1,121 @@ +/* + * 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.services.rpcv2cbor; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.core.useragent.BusinessMetricCollection.METRIC_SEARCH_PATTERN; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; +import software.amazon.awssdk.services.protocolsmithyrpcv2.ProtocolSmithyrpcv2AsyncClient; +import software.amazon.awssdk.services.protocolsmithyrpcv2.ProtocolSmithyrpcv2Client; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; +import software.amazon.awssdk.utils.StringInputStream; + +class RpcV2CborUserAgentTest { + private static final String USER_AGENT_HEADER_NAME = "User-Agent"; + private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + + private MockSyncHttpClient mockHttpClient; + private MockAsyncHttpClient mockAsyncHttpClient; + + @BeforeEach + public void setup() { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockResponse()); + + mockAsyncHttpClient = new MockAsyncHttpClient(); + mockAsyncHttpClient.stubNextResponse(mockResponse()); + } + + @Test + void when_rpcV2CborProtocolIsUsed_correctMetricIsAdded() { + ProtocolSmithyrpcv2Client client = ProtocolSmithyrpcv2Client.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockHttpClient) + .build(); + + client.operationWithNoInputOrOutput(r -> {}); + + String userAgent = getUserAgentFromLastRequest(); + assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("M")); + } + + @Test + void when_rpcV2CborProtocolIsUsedAsync_correctMetricIsAdded() { + ProtocolSmithyrpcv2AsyncClient asyncClient = ProtocolSmithyrpcv2AsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockAsyncHttpClient) + .build(); + + asyncClient.operationWithNoInputOrOutput(r -> {}).join(); + + String userAgent = getUserAgentFromLastAsyncRequest(); + assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("M")); + } + + @Test + void when_nonRpcV2CborProtocolIsUsed_rpcV2CborMetricIsNotAdded() { + ProtocolRestJsonClient client = ProtocolRestJsonClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockHttpClient) + .build(); + + client.allTypes(r -> {}); + + String userAgent = getUserAgentFromLastRequest(); + assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("M")); + } + + private String getUserAgentFromLastRequest() { + SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + return userAgentHeaders.get(0); + } + + private String getUserAgentFromLastAsyncRequest() { + SdkHttpRequest lastRequest = mockAsyncHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + return userAgentHeaders.get(0); + } + + private static HttpExecuteResponse mockResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream("{}"))) + .build(); + } +}