Skip to content

Commit eafcfce

Browse files
committed
API consistency: GetObjectAttributesOutput / Checksum type
Checking AWS APIs for changes and S3Mock for implementations and tests. Fixes #2340
1 parent 39940fe commit eafcfce

File tree

18 files changed

+187
-83
lines changed

18 files changed

+187
-83
lines changed

integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectIT.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import software.amazon.awssdk.services.s3.S3AsyncClient
3232
import software.amazon.awssdk.services.s3.S3Client
3333
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm
3434
import software.amazon.awssdk.services.s3.model.ChecksumMode
35+
import software.amazon.awssdk.services.s3.model.ChecksumType
3536
import software.amazon.awssdk.services.s3.model.HeadObjectRequest
3637
import software.amazon.awssdk.services.s3.model.NoSuchBucketException
3738
import software.amazon.awssdk.services.s3.model.NoSuchKeyException
@@ -486,10 +487,11 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
486487
}.also {
487488
//
488489
assertThat(it.eTag()).isEqualTo(eTag.trim('"'))
489-
//default storageClass is STANDARD, which is never returned from APIs
490+
//default storageClass is STANDARD, which is never returned from APIs except by GetObjectAttributes
490491
assertThat(it.storageClass()).isEqualTo(StorageClass.STANDARD)
491492
assertThat(it.objectSize()).isEqualTo(File(UPLOAD_FILE_NAME).length())
492493
assertThat(it.checksum().checksumSHA1()).isEqualTo(expectedChecksum)
494+
assertThat(it.checksum().checksumType()).isEqualTo(ChecksumType.FULL_OBJECT)
493495
}
494496
}
495497

@@ -1211,7 +1213,7 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
12111213
val uploadFile = File(UPLOAD_FILE_NAME)
12121214
val bucketName = givenBucket(testInfo)
12131215

1214-
val s3Client = this@GetPutDeleteObjectIT.createS3Client(chunkedEncodingEnabled = uploadChunked)
1216+
val s3Client = createS3Client(chunkedEncodingEnabled = uploadChunked)
12151217

12161218
s3Client.putObject({
12171219
it.bucket(bucketName)

server/src/main/java/com/adobe/testing/s3mock/dto/Checksum.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,20 @@
1616

1717
package com.adobe.testing.s3mock.dto;
1818

19+
import com.adobe.testing.S3Verified;
1920
import com.fasterxml.jackson.annotation.JsonProperty;
2021

2122
/**
2223
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Checksum.html">API Reference</a>.
2324
*/
25+
@S3Verified(year = 2025)
2426
public record Checksum(
25-
@JsonProperty("ChecksumCRC32")
26-
String checksumCRC32,
27-
@JsonProperty("ChecksumCRC32C")
28-
String checksumCRC32C,
29-
@JsonProperty("ChecksumSHA1")
30-
String checksumSHA1,
31-
@JsonProperty("ChecksumSHA256")
32-
String checksumSHA256,
33-
@JsonProperty("ChecksumCRC64NVME")
34-
String checksumCRC64NVME
27+
@JsonProperty("ChecksumCRC32") String checksumCRC32,
28+
@JsonProperty("ChecksumCRC32C") String checksumCRC32C,
29+
@JsonProperty("ChecksumCRC64NVME") String checksumCRC64NVME,
30+
@JsonProperty("ChecksumSHA1") String checksumSHA1,
31+
@JsonProperty("ChecksumSHA256") String checksumSHA256,
32+
@JsonProperty("ChecksumType") ChecksumType checksumType
3533
) {
3634

3735
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2017-2025 Adobe.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.adobe.testing.s3mock.dto;
18+
19+
import com.adobe.testing.S3Verified;
20+
import com.fasterxml.jackson.annotation.JsonCreator;
21+
import com.fasterxml.jackson.annotation.JsonValue;
22+
23+
/**
24+
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Checksum.html">API Reference</a>.
25+
*/
26+
@S3Verified(year = 2025)
27+
public enum ChecksumType {
28+
COMPOSITE("COMPOSITE"),
29+
FULL_OBJECT("FULL_OBJECT");
30+
31+
private final String value;
32+
33+
@JsonCreator
34+
ChecksumType(String value) {
35+
this.value = value;
36+
}
37+
38+
public static ChecksumType fromString(String value) {
39+
return switch (value) {
40+
case "composite" -> COMPOSITE;
41+
case "full_object" -> FULL_OBJECT;
42+
default -> null;
43+
};
44+
}
45+
46+
@Override
47+
@JsonValue
48+
public String toString() {
49+
return String.valueOf(this.value);
50+
}
51+
}

server/src/main/java/com/adobe/testing/s3mock/dto/GetObjectAttributesOutput.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,29 @@
1616

1717
package com.adobe.testing.s3mock.dto;
1818

19+
import com.adobe.testing.S3Verified;
1920
import com.adobe.testing.s3mock.store.S3ObjectMetadata;
2021
import com.fasterxml.jackson.annotation.JsonProperty;
2122
import com.fasterxml.jackson.annotation.JsonRootName;
2223
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
2324
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
2425
import java.util.List;
2526

27+
/**
28+
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html">API Reference</a>.
29+
*/
30+
@S3Verified(year = 2025)
2631
@JsonRootName("GetObjectAttributesOutput")
2732
public record GetObjectAttributesOutput(
28-
@JsonProperty("Checksum")
29-
Checksum checksum,
30-
@JsonProperty("ETag")
31-
String etag,
32-
@JsonProperty("ObjectParts")
33+
@JsonProperty("Checksum") Checksum checksum,
34+
//This response does not use eTag as a header, so it must not contain the quotation marks.
35+
@JsonProperty("ETag") String etag,
3336
@JacksonXmlElementWrapper(useWrapping = false)
34-
List<GetObjectAttributesParts> objectParts,
35-
@JsonProperty("ObjectSize")
36-
Long objectSize,
37-
@JsonProperty("StorageClass")
38-
StorageClass storageClass,
37+
@JsonProperty("ObjectParts") List<GetObjectAttributesParts> objectParts,
38+
@JsonProperty("ObjectSize") Long objectSize,
39+
@JsonProperty("StorageClass") StorageClass storageClass,
3940
//workaround for adding xmlns attribute to root element only.
40-
@JacksonXmlProperty(isAttribute = true, localName = "xmlns")
41-
String xmlns
41+
@JacksonXmlProperty(isAttribute = true, localName = "xmlns") String xmlns
4242
) {
4343

4444
public GetObjectAttributesOutput {

server/src/main/java/com/adobe/testing/s3mock/dto/GetObjectAttributesParts.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,23 @@
1616

1717
package com.adobe.testing.s3mock.dto;
1818

19+
import com.adobe.testing.S3Verified;
1920
import com.fasterxml.jackson.annotation.JsonProperty;
2021
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
2122
import java.util.List;
2223

2324
/**
2425
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributesParts.html">API Reference</a>.
2526
*/
27+
@S3Verified(year = 2025)
2628
public record GetObjectAttributesParts(
27-
@JsonProperty("MaxParts")
28-
int maxParts,
29-
@JsonProperty("IsTruncated")
30-
boolean isTruncated,
31-
@JsonProperty("NextPartNumberMarker")
32-
int nextPartNumberMarker,
33-
@JsonProperty("PartNumberMarker")
34-
int partNumberMarker,
35-
@JsonProperty("TotalPartsCount")
36-
int totalPartsCount,
37-
@JsonProperty("Parts")
29+
@JsonProperty("IsTruncated") boolean isTruncated,
30+
@JsonProperty("MaxParts") int maxParts,
31+
@JsonProperty("NextPartNumberMarker") int nextPartNumberMarker,
32+
@JsonProperty("PartNumberMarker") int partNumberMarker,
3833
@JacksonXmlElementWrapper(useWrapping = false)
39-
List<ObjectPart> parts
34+
@JsonProperty("Parts") List<ObjectPart> parts,
35+
@JsonProperty("TotalPartsCount") int totalPartsCount
4036
) {
4137

4238
}

server/src/main/java/com/adobe/testing/s3mock/service/ObjectService.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.adobe.testing.s3mock.dto.AccessControlPolicy;
3030
import com.adobe.testing.s3mock.dto.Checksum;
3131
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
32+
import com.adobe.testing.s3mock.dto.ChecksumType;
3233
import com.adobe.testing.s3mock.dto.Delete;
3334
import com.adobe.testing.s3mock.dto.DeleteResult;
3435
import com.adobe.testing.s3mock.dto.DeletedS3Object;
@@ -147,7 +148,7 @@ public S3ObjectMetadata putS3Object(String bucketName,
147148
}
148149
return objectStore.storeS3ObjectMetadata(bucketMetadata, id, key, contentType, storeHeaders,
149150
path, userMetadata, encryptionHeaders, null, tags,
150-
checksumAlgorithm, checksum, owner, storageClass);
151+
checksumAlgorithm, checksum, owner, storageClass, ChecksumType.FULL_OBJECT);
151152
}
152153

153154
public DeleteResult deleteObjects(String bucketName, Delete delete) {
@@ -369,11 +370,11 @@ public static Checksum getChecksum(S3ObjectMetadata s3ObjectMetadata) {
369370
var checksumAlgorithm = s3ObjectMetadata.checksumAlgorithm();
370371
if (checksumAlgorithm != null) {
371372
return new Checksum(
372-
checksumAlgorithm == ChecksumAlgorithm.CRC32 ? s3ObjectMetadata.checksum() : null,
373-
checksumAlgorithm == ChecksumAlgorithm.CRC32C ? s3ObjectMetadata.checksum() : null,
374-
checksumAlgorithm == ChecksumAlgorithm.SHA1 ? s3ObjectMetadata.checksum() : null,
375-
checksumAlgorithm == ChecksumAlgorithm.SHA256 ? s3ObjectMetadata.checksum() : null,
376-
checksumAlgorithm == ChecksumAlgorithm.CRC64NVME ? s3ObjectMetadata.checksum() : null
373+
checksumAlgorithm == ChecksumAlgorithm.CRC32 ? s3ObjectMetadata.checksum() : null,
374+
checksumAlgorithm == ChecksumAlgorithm.CRC32C ? s3ObjectMetadata.checksum() : null,
375+
checksumAlgorithm == ChecksumAlgorithm.CRC64NVME ? s3ObjectMetadata.checksum() : null, checksumAlgorithm == ChecksumAlgorithm.SHA1 ? s3ObjectMetadata.checksum() : null,
376+
checksumAlgorithm == ChecksumAlgorithm.SHA256 ? s3ObjectMetadata.checksum() : null,
377+
s3ObjectMetadata.checksumType()
377378
);
378379
}
379380
return null;

server/src/main/java/com/adobe/testing/s3mock/store/MultipartStore.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static org.apache.commons.lang3.StringUtils.isBlank;
2626

2727
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
28+
import com.adobe.testing.s3mock.dto.ChecksumType;
2829
import com.adobe.testing.s3mock.dto.CompleteMultipartUploadResult;
2930
import com.adobe.testing.s3mock.dto.CompletedPart;
3031
import com.adobe.testing.s3mock.dto.MultipartUpload;
@@ -269,7 +270,8 @@ public CompleteMultipartUploadResult completeMultipartUpload(BucketMetadata buck
269270
uploadInfo.checksumAlgorithm(),
270271
checksumFor,
271272
uploadInfo.upload().owner(),
272-
uploadInfo.storageClass()
273+
uploadInfo.storageClass(),
274+
ChecksumType.COMPOSITE
273275
);
274276
FileUtils.deleteDirectory(partFolder.toFile());
275277
return CompleteMultipartUploadResult.from(location, uploadInfo.bucket(),

server/src/main/java/com/adobe/testing/s3mock/store/ObjectStore.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.adobe.testing.s3mock.dto.AccessControlPolicy;
2525
import com.adobe.testing.s3mock.dto.CanonicalUser;
2626
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
27+
import com.adobe.testing.s3mock.dto.ChecksumType;
2728
import com.adobe.testing.s3mock.dto.Grant;
2829
import com.adobe.testing.s3mock.dto.LegalHold;
2930
import com.adobe.testing.s3mock.dto.Owner;
@@ -106,7 +107,8 @@ public S3ObjectMetadata storeS3ObjectMetadata(BucketMetadata bucket,
106107
ChecksumAlgorithm checksumAlgorithm,
107108
String checksum,
108109
Owner owner,
109-
StorageClass storageClass) {
110+
StorageClass storageClass,
111+
ChecksumType checksumType) {
110112
lockStore.putIfAbsent(id, new Object());
111113
synchronized (lockStore.get(id)) {
112114
createObjectRootFolder(bucket, id);
@@ -148,7 +150,8 @@ public S3ObjectMetadata storeS3ObjectMetadata(BucketMetadata bucket,
148150
storageClass,
149151
null,
150152
versionId,
151-
false
153+
false,
154+
checksumType
152155
);
153156
writeMetafile(bucket, s3ObjectMetadata);
154157
return s3ObjectMetadata;
@@ -192,7 +195,8 @@ public void storeObjectTags(BucketMetadata bucket, UUID id, String versionId, Li
192195
s3ObjectMetadata.storageClass(),
193196
s3ObjectMetadata.policy(),
194197
s3ObjectMetadata.versionId(),
195-
s3ObjectMetadata.deleteMarker()
198+
s3ObjectMetadata.deleteMarker(),
199+
s3ObjectMetadata.checksumType()
196200
));
197201
}
198202
}
@@ -229,7 +233,8 @@ public void storeLegalHold(BucketMetadata bucket, UUID id, String versionId,
229233
s3ObjectMetadata.storageClass(),
230234
s3ObjectMetadata.policy(),
231235
s3ObjectMetadata.versionId(),
232-
s3ObjectMetadata.deleteMarker()
236+
s3ObjectMetadata.deleteMarker(),
237+
s3ObjectMetadata.checksumType()
233238
));
234239
}
235240
}
@@ -266,7 +271,8 @@ public void storeAcl(BucketMetadata bucket, UUID id, String versionId,
266271
s3ObjectMetadata.storageClass(),
267272
policy,
268273
s3ObjectMetadata.versionId(),
269-
s3ObjectMetadata.deleteMarker()
274+
s3ObjectMetadata.deleteMarker(),
275+
s3ObjectMetadata.checksumType()
270276
)
271277
);
272278
}
@@ -311,7 +317,8 @@ public void storeRetention(BucketMetadata bucket, UUID id, String versionId,
311317
s3ObjectMetadata.storageClass(),
312318
s3ObjectMetadata.policy(),
313319
s3ObjectMetadata.versionId(),
314-
s3ObjectMetadata.deleteMarker()
320+
s3ObjectMetadata.deleteMarker(),
321+
s3ObjectMetadata.checksumType()
315322
));
316323
}
317324
}
@@ -435,7 +442,8 @@ public S3ObjectMetadata copyS3Object(BucketMetadata sourceBucket,
435442
sourceObject.checksumAlgorithm(),
436443
sourceObject.checksum(),
437444
sourceObject.owner(),
438-
storageClass != null ? storageClass : sourceObject.storageClass()
445+
storageClass != null ? storageClass : sourceObject.storageClass(),
446+
sourceObject.checksumType()
439447
);
440448
}
441449
}
@@ -483,7 +491,8 @@ public S3ObjectMetadata pretendToCopyS3Object(BucketMetadata sourceBucket,
483491
storageClass != null ? storageClass : sourceObject.storageClass(),
484492
sourceObject.policy(),
485493
sourceObject.versionId(),
486-
sourceObject.deleteMarker()
494+
sourceObject.deleteMarker(),
495+
sourceObject.checksumType()
487496
);
488497
writeMetafile(sourceBucket, s3ObjectMetadata);
489498
return s3ObjectMetadata;

server/src/main/java/com/adobe/testing/s3mock/store/S3ObjectMetadata.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.adobe.testing.s3mock.dto.AccessControlPolicy;
2222
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
23+
import com.adobe.testing.s3mock.dto.ChecksumType;
2324
import com.adobe.testing.s3mock.dto.LegalHold;
2425
import com.adobe.testing.s3mock.dto.Owner;
2526
import com.adobe.testing.s3mock.dto.Retention;
@@ -59,7 +60,8 @@ public record S3ObjectMetadata(
5960
StorageClass storageClass,
6061
AccessControlPolicy policy,
6162
String versionId,
62-
boolean deleteMarker
63+
boolean deleteMarker,
64+
ChecksumType checksumType
6365
) {
6466

6567
public S3ObjectMetadata {
@@ -70,6 +72,7 @@ public record S3ObjectMetadata(
7072
storeHeaders = storeHeaders == null ? Collections.emptyMap() : storeHeaders;
7173
encryptionHeaders = encryptionHeaders == null ? Collections.emptyMap() : encryptionHeaders;
7274
storageClass = storageClass == StorageClass.STANDARD ? null : storageClass;
75+
checksumType = checksumType == null ? ChecksumType.FULL_OBJECT : checksumType;
7376
}
7477

7578
public static S3ObjectMetadata deleteMarker(S3ObjectMetadata metadata, String versionId) {
@@ -93,7 +96,8 @@ public static S3ObjectMetadata deleteMarker(S3ObjectMetadata metadata, String ve
9396
metadata.storageClass(),
9497
metadata.policy(),
9598
versionId,
96-
true
99+
true,
100+
metadata.checksumType()
97101
);
98102
}
99103
}

server/src/test/kotlin/com/adobe/testing/s3mock/ObjectControllerTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.adobe.testing.s3mock
1818
import com.adobe.testing.s3mock.dto.AccessControlPolicy
1919
import com.adobe.testing.s3mock.dto.Bucket
2020
import com.adobe.testing.s3mock.dto.CanonicalUser
21+
import com.adobe.testing.s3mock.dto.ChecksumType
2122
import com.adobe.testing.s3mock.dto.Grant
2223
import com.adobe.testing.s3mock.dto.Mode
2324
import com.adobe.testing.s3mock.dto.Owner
@@ -643,7 +644,8 @@ internal class ObjectControllerTest : BaseControllerTest() {
643644
null,
644645
null,
645646
null,
646-
false
647+
false,
648+
ChecksumType.FULL_OBJECT
647649
)
648650
}
649651

0 commit comments

Comments
 (0)