diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java b/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java index d4e1b02d5639..a1b03a6b60d8 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java @@ -160,6 +160,11 @@ private void postprocessModifyMemberProperty(ShapeModel shapeModel, String membe .getUnmarshallLocationName()); } + if (modifyModel.isIgnoreDataTypeConversionFailures()) { + MemberModel memberModel = shapeModel.findMemberModelByC2jName(memberName); + memberModel.ignoreDataTypeConversionFailures(true); + } + } /** diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java index dffaf7d0d40b..468ec7154fa3 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java @@ -59,6 +59,11 @@ public class ModifyModelShapeModifier { private String unmarshallLocationName; + /** + * Indicates whether data type conversion failures are to be ignored + */ + private boolean ignoreDataTypeConversionFailures; + public String getDeprecatedMessage() { return deprecatedMessage; } @@ -130,4 +135,12 @@ public String getEmitAsType() { public void setEmitAsType(String emitAsType) { this.emitAsType = emitAsType; } + + public void setIgnoreDataTypeConversionFailures(boolean ignoreDataTypeConversionFailures) { + this.ignoreDataTypeConversionFailures = ignoreDataTypeConversionFailures; + } + + public boolean isIgnoreDataTypeConversionFailures() { + return ignoreDataTypeConversionFailures; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java index f6a870409ebd..fddf93d4d72d 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java @@ -115,6 +115,8 @@ public class MemberModel extends DocumentationModel { private ContextParam contextParam; + private boolean ignoreDataTypeConversionFailures; + public String getName() { return name; } @@ -775,4 +777,12 @@ public ContextParam getContextParam() { public void setContextParam(ContextParam contextParam) { this.contextParam = contextParam; } + + public void ignoreDataTypeConversionFailures(boolean ignoreDataTypeConversionFailures) { + this.ignoreDataTypeConversionFailures = ignoreDataTypeConversionFailures; + } + + public boolean ignoreDataTypeConversionFailures() { + return ignoreDataTypeConversionFailures; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java index 8d79d83928ae..d10ab65a8a27 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java @@ -39,6 +39,7 @@ import software.amazon.awssdk.core.SdkField; import software.amazon.awssdk.core.protocol.MarshallLocation; import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.DataTypeConversionFailureHandlingTrait; import software.amazon.awssdk.core.traits.DefaultValueTrait; import software.amazon.awssdk.core.traits.JsonValueTrait; import software.amazon.awssdk.core.traits.ListTrait; @@ -186,6 +187,10 @@ private CodeBlock traits(MemberModel m) { if (m.isXmlAttribute()) { traits.add(createXmlAttributeTrait()); } + + if (m.ignoreDataTypeConversionFailures()) { + traits.add(createDataTypeConversionFailureHandlingTrait()); + } if (customizationConfig.isRequiredTraitValidationEnabled() && m.isRequired()) { traits.add(createRequiredTrait()); } @@ -206,6 +211,12 @@ private boolean attachPayloadTraitToMember(MemberModel m) { .equals(m.getC2jName()); } + private CodeBlock createDataTypeConversionFailureHandlingTrait() { + return CodeBlock.builder() + .add("new $T()", ClassName.get(DataTypeConversionFailureHandlingTrait.class)) + .build(); + } + private CodeBlock createTimestampFormatTrait(MemberModel m) { TimestampFormatTrait.Format format = TimestampFormatTrait.Format.fromString(m.getTimestampFormat()); ClassName traitClass = ClassName.get(TimestampFormatTrait.class); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config index 213183ce5a87..0375c733e912 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config @@ -26,6 +26,11 @@ "deprecated": true, "deprecatedMessage": "This field is modified as deprecated." } + }, + { + "MemberIgnoreDataTypeFailureHandling": { + "ignoreDataTypeConversionFailures": "true" + } } ] }, diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java index 05cf3b39317c..c14ae6ee3ede 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java @@ -14,6 +14,7 @@ import software.amazon.awssdk.core.SdkPojo; import software.amazon.awssdk.core.protocol.MarshallLocation; import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.DataTypeConversionFailureHandlingTrait; import software.amazon.awssdk.core.traits.LocationTrait; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -45,8 +46,18 @@ public final class OperationWithDeprecatedMemberRequest extends JsonProtocolTest .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("UndeprecatedMember").build()) .build(); + private static final SdkField MEMBER_IGNORE_DATA_TYPE_FAILURE_HANDLING_FIELD = SdkField + . builder(MarshallingType.STRING) + .memberName("MemberIgnoreDataTypeFailureHandling") + .getter(getter(OperationWithDeprecatedMemberRequest::memberIgnoreDataTypeFailureHandling)) + .setter(setter(Builder::memberIgnoreDataTypeFailureHandling)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD) + .locationName("MemberIgnoreDataTypeFailureHandling").build(), new DataTypeConversionFailureHandlingTrait()) + .build(); + private static final List> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList( - MEMBER_MODELED_AS_DEPRECATED_FIELD, MEMBER_MODIFIED_AS_DEPRECATED_FIELD, UNDEPRECATED_MEMBER_FIELD)); + MEMBER_MODELED_AS_DEPRECATED_FIELD, MEMBER_MODIFIED_AS_DEPRECATED_FIELD, UNDEPRECATED_MEMBER_FIELD, + MEMBER_IGNORE_DATA_TYPE_FAILURE_HANDLING_FIELD)); private final String memberModeledAsDeprecated; @@ -54,11 +65,14 @@ public final class OperationWithDeprecatedMemberRequest extends JsonProtocolTest private final String undeprecatedMember; + private final String memberIgnoreDataTypeFailureHandling; + private OperationWithDeprecatedMemberRequest(BuilderImpl builder) { super(builder); this.memberModeledAsDeprecated = builder.memberModeledAsDeprecated; this.memberModifiedAsDeprecated = builder.memberModifiedAsDeprecated; this.undeprecatedMember = builder.undeprecatedMember; + this.memberIgnoreDataTypeFailureHandling = builder.memberIgnoreDataTypeFailureHandling; } /** @@ -92,6 +106,15 @@ public final String undeprecatedMember() { return undeprecatedMember; } + /** + * Returns the value of the MemberIgnoreDataTypeFailureHandling property for this object. + * + * @return The value of the MemberIgnoreDataTypeFailureHandling property for this object. + */ + public final String memberIgnoreDataTypeFailureHandling() { + return memberIgnoreDataTypeFailureHandling; + } + @Override public Builder toBuilder() { return new BuilderImpl(this); @@ -112,6 +135,7 @@ public final int hashCode() { hashCode = 31 * hashCode + Objects.hashCode(memberModeledAsDeprecated()); hashCode = 31 * hashCode + Objects.hashCode(memberModifiedAsDeprecated()); hashCode = 31 * hashCode + Objects.hashCode(undeprecatedMember()); + hashCode = 31 * hashCode + Objects.hashCode(memberIgnoreDataTypeFailureHandling()); return hashCode; } @@ -134,7 +158,8 @@ public final boolean equalsBySdkFields(Object obj) { OperationWithDeprecatedMemberRequest other = (OperationWithDeprecatedMemberRequest) obj; return Objects.equals(memberModeledAsDeprecated(), other.memberModeledAsDeprecated()) && Objects.equals(memberModifiedAsDeprecated(), other.memberModifiedAsDeprecated()) - && Objects.equals(undeprecatedMember(), other.undeprecatedMember()); + && Objects.equals(undeprecatedMember(), other.undeprecatedMember()) + && Objects.equals(memberIgnoreDataTypeFailureHandling(), other.memberIgnoreDataTypeFailureHandling()); } /** @@ -146,7 +171,7 @@ public final String toString() { return ToString.builder("OperationWithDeprecatedMemberRequest") .add("MemberModeledAsDeprecated", memberModeledAsDeprecated()) .add("MemberModifiedAsDeprecated", memberModifiedAsDeprecated()).add("UndeprecatedMember", undeprecatedMember()) - .build(); + .add("MemberIgnoreDataTypeFailureHandling", memberIgnoreDataTypeFailureHandling()).build(); } public final Optional getValueForField(String fieldName, Class clazz) { @@ -157,6 +182,8 @@ public final Optional getValueForField(String fieldName, Class clazz) return Optional.ofNullable(clazz.cast(memberModifiedAsDeprecated())); case "UndeprecatedMember": return Optional.ofNullable(clazz.cast(undeprecatedMember())); + case "MemberIgnoreDataTypeFailureHandling": + return Optional.ofNullable(clazz.cast(memberIgnoreDataTypeFailureHandling())); default: return Optional.empty(); } @@ -208,6 +235,15 @@ public interface Builder extends JsonProtocolTestsRequest.Builder, SdkPojo, */ Builder undeprecatedMember(String undeprecatedMember); + /** + * Sets the value of the MemberIgnoreDataTypeFailureHandling property for this object. + * + * @param memberIgnoreDataTypeFailureHandling + * The new value for the MemberIgnoreDataTypeFailureHandling property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder memberIgnoreDataTypeFailureHandling(String memberIgnoreDataTypeFailureHandling); + @Override Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration); @@ -222,6 +258,8 @@ static final class BuilderImpl extends JsonProtocolTestsRequest.BuilderImpl impl private String undeprecatedMember; + private String memberIgnoreDataTypeFailureHandling; + private BuilderImpl() { } @@ -230,6 +268,7 @@ private BuilderImpl(OperationWithDeprecatedMemberRequest model) { memberModeledAsDeprecated(model.memberModeledAsDeprecated); memberModifiedAsDeprecated(model.memberModifiedAsDeprecated); undeprecatedMember(model.undeprecatedMember); + memberIgnoreDataTypeFailureHandling(model.memberIgnoreDataTypeFailureHandling); } @Deprecated @@ -280,6 +319,20 @@ public final Builder undeprecatedMember(String undeprecatedMember) { return this; } + public final String getMemberIgnoreDataTypeFailureHandling() { + return memberIgnoreDataTypeFailureHandling; + } + + public final void setMemberIgnoreDataTypeFailureHandling(String memberIgnoreDataTypeFailureHandling) { + this.memberIgnoreDataTypeFailureHandling = memberIgnoreDataTypeFailureHandling; + } + + @Override + public final Builder memberIgnoreDataTypeFailureHandling(String memberIgnoreDataTypeFailureHandling) { + this.memberIgnoreDataTypeFailureHandling = memberIgnoreDataTypeFailureHandling; + return this; + } + @Override public Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration) { super.overrideConfiguration(overrideConfiguration); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json index fb3e0a548d9e..e846f69f04ed 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json @@ -247,7 +247,8 @@ "deprecatedMessage": "This field is modeled as deprecated." }, "MemberModifiedAsDeprecated":{"shape": "String"}, - "UndeprecatedMember": {"shape": "String"} + "UndeprecatedMember": {"shape": "String"}, + "MemberIgnoreDataTypeFailureHandling":{"shape": "String"} } }, "OperationWithDeprecatedMemberResponse":{ diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java index fe541ad104a8..4f1121da3dcc 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java @@ -26,6 +26,7 @@ import software.amazon.awssdk.core.SdkField; import software.amazon.awssdk.protocols.core.StringToValueConverter; import software.amazon.awssdk.protocols.query.unmarshall.XmlElement; +import software.amazon.awssdk.utils.Logger; @SdkInternalApi public final class HeaderUnmarshaller { @@ -60,6 +61,7 @@ private HeaderUnmarshaller() { } private static class SimpleHeaderUnmarshaller implements XmlUnmarshaller { + private static final Logger log = Logger.loggerFor(SimpleHeaderUnmarshaller.class); private final StringToValueConverter.StringToValue stringToValue; @@ -69,9 +71,17 @@ private SimpleHeaderUnmarshaller(StringToValueConverter.StringToValue stringT @Override public T unmarshall(XmlUnmarshallerContext context, List content, SdkField field) { - return context.response().firstMatchingHeader(field.locationName()) - .map(s -> stringToValue.convert(s, field)) - .orElse(null); + try { + return context.response().firstMatchingHeader(field.locationName()) + .map(s -> stringToValue.convert(s, field)) + .orElse(null); + } catch (RuntimeException e) { + log.warn(() -> "Exception found while parsing response header " , e); + if (field.ignoreDataTypeConversionFailures()) { + return null; + } + throw e; + } } } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java index 161754efa1f7..063ef85f2cd6 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java @@ -25,6 +25,7 @@ import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.core.protocol.MarshallLocation; import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.DataTypeConversionFailureHandlingTrait; import software.amazon.awssdk.core.traits.DefaultValueTrait; import software.amazon.awssdk.core.traits.LocationTrait; import software.amazon.awssdk.core.traits.Trait; @@ -86,6 +87,16 @@ public String locationName() { return locationName; } + /** + * @return whether data-type conversion errors are to be ignored + */ + public boolean ignoreDataTypeConversionFailures() { + DataTypeConversionFailureHandlingTrait dataTypeConversionFailureHandlingTrait = + getTrait(DataTypeConversionFailureHandlingTrait.class); + + return dataTypeConversionFailureHandlingTrait != null; + } + /** * @return The location name to use when unmarshalling. This is only needed for AWS/Query or EC2 services. All * other services should use {@link #locationName} for both marshalling and unmarshalling. diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/traits/DataTypeConversionFailureHandlingTrait.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/traits/DataTypeConversionFailureHandlingTrait.java new file mode 100644 index 000000000000..243ff2dac30b --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/traits/DataTypeConversionFailureHandlingTrait.java @@ -0,0 +1,22 @@ +/* + * 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.core.traits; + +import software.amazon.awssdk.annotations.SdkProtectedApi; + +@SdkProtectedApi +public class DataTypeConversionFailureHandlingTrait implements Trait { +} diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index 0f06cc755691..0e573b1c8612 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -27,6 +27,77 @@ } ] }, + "PutObjectRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "WriteGetObjectResponseRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "GetObjectOutput": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp", + "deprecated": "true", + "deprecatedMessage": "Usage of the Expires field is deprecated in favor of the more flexible and less error prone ExpiresString", + "ignoreDataTypeConversionFailures": "true" + } + } + ], + "inject": [ + { + "ExpiresString": { + "shape": "Expiration", + "documentation":"

The date and time at which the object is no longer cacheable

", + "location":"header", + "locationName":"Expires" + } + } + ] + }, + "HeadObjectOutput": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp", + "deprecated": "true", + "deprecatedMessage": "Usage of the Expires field is deprecated in favor of the more flexible and less error prone ExpiresString", + "ignoreDataTypeConversionFailures": "true" + } + } + ], + "inject": [ + { + "ExpiresString": { + "shape": "Expiration", + "documentation":"

The date and time at which the object is no longer cacheable

", + "location":"header", + "locationName":"Expires" + } + } + ] + }, + "CreateMultipartUploadRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, "UploadPartRequest": { "inject": [ { @@ -67,7 +138,11 @@ "Key": { "emitPropertyName": "DestinationKey", "existingNameDeprecated": true + }, + "Expires": { + "emitAsType": "timestamp" } + } ] }, diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java new file mode 100644 index 000000000000..457d0388a48c --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java @@ -0,0 +1,90 @@ +/* + * 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.s3; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.io.IOException; +import java.net.URI; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; + +@WireMockTest +public class ExpiresHeaderDataTypeErrorTest { + + S3Client s3Client; + private final String TEST_DATE = "2034-02-01T00:00:00Z"; + + @BeforeEach + public void initWireMock(WireMockRuntimeInfo wm) { + s3Client = S3Client.builder().endpointOverride(URI.create(wm.getHttpBaseUrl())) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .build(); + } + + @Test + public void headObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { + + stubFor(any(anyUrl()) + .willReturn(aResponse() + .withHeader("Expires", TEST_DATE) + .withBody("Hello world!"))); + + assertThatCode(() -> s3Client.headObject(r -> { + r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key"); + })) + .doesNotThrowAnyException(); + + assertThat(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).expires()).isNull(); + + assertThat(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).expiresString()).isEqualTo(TEST_DATE); + + } + + @Test + public void getObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { + + stubFor(any(anyUrl()) + .willReturn(aResponse() + .withHeader("Expires", TEST_DATE) + .withBody("Hello world!"))); + + Assertions.assertThatCode(() -> s3Client.headObject(r -> { + r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key"); + })) + .doesNotThrowAnyException(); + + assertThat(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).response().expires()).isNull(); + + assertThat(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).response().expiresString()).isEqualTo(TEST_DATE); + + } +}