From f9ce54852ea231b95c52960917166011e753ffb3 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Thu, 26 Jun 2025 14:41:03 -0400 Subject: [PATCH] chore: add UnifiedOpts.Opts#getHasher() to return a hasher relative to the set of Opts defined for an operation. Add `UnifiedOpts.HasherSelector` (also add `UnifiedOptions.BucketObjectHmacKeyAllOpt` which extends all ``{Bucket,Object,HmacKey}{Target,List,Source}Opt` and provides an anchor for those opts which apply to all 9 permutations [UserProject & Headers]) Add Opts#getHasher() that will return Hasher.defaultInstance() or a hasher defined based on the last occurring HasherProvider present in the instance of Opts. Both Crc32cMatch and Md5Match are configured to return Hasher.noop(). If an explicit checksum value is provided by the customer, use it and disable our automatic crc32c calculation. --- .../com/google/cloud/storage/UnifiedOpts.java | 97 ++++++++++++------- .../google/cloud/storage/UnifiedOptsTest.java | 29 +++++- 2 files changed, 90 insertions(+), 36 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java index 77cb1344a3..2dae52b8e4 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java @@ -281,6 +281,10 @@ interface ProjectAsSource { O asSource(); } + interface HasherSelector extends BucketObjectHmacKeyAllOpt { + Hasher getHasher(); + } + /** * This class extends off {@link ObjectSourceOpt} and {@link ObjectTargetOpt} in order to satisfy * some the shimming constraints of the subclasses of {@link OptionShim}. @@ -589,7 +593,11 @@ static Headers extraHeaders(ImmutableMap extraHeaders) { return new Headers(extraHeaders); } - static final class Crc32cMatch implements ObjectTargetOpt { + static DefaultHasherSelector defaultHasherSelector() { + return DefaultHasherSelector.INSTANCE; + } + + static final class Crc32cMatch implements ObjectTargetOpt, HasherSelector { private static final long serialVersionUID = 8172282701777561769L; private final int val; @@ -630,6 +638,11 @@ public Mapper bidiWriteObject() { }; } + @Override + public Hasher getHasher() { + return Hasher.noop(); + } + @Override public int hashCode() { return Objects.hash(val); @@ -1315,7 +1328,7 @@ public Mapper listObjects() { } @Deprecated - static final class Md5Match implements ObjectTargetOpt { + static final class Md5Match implements ObjectTargetOpt, HasherSelector { private static final long serialVersionUID = 5237207911268363887L; private final String val; @@ -1358,6 +1371,11 @@ public Mapper bidiWriteObject() { }; } + @Override + public Hasher getHasher() { + return Hasher.noop(); + } + @Override public int hashCode() { return Objects.hash(val); @@ -1983,8 +2001,8 @@ public Mapper listObjects() { } } - static final class UserProject extends RpcOptVal - implements BucketSourceOpt, + interface BucketObjectHmacKeyAllOpt + extends BucketSourceOpt, BucketTargetOpt, BucketListOpt, ObjectSourceOpt, @@ -1993,6 +2011,18 @@ static final class UserProject extends RpcOptVal HmacKeySourceOpt, HmacKeyTargetOpt, HmacKeyListOpt { + @Override + default Mapper rewriteObject() { + return Mapper.identity(); + } + + @Override + default Mapper moveObject() { + return Mapper.identity(); + } + } + + static final class UserProject extends RpcOptVal implements BucketObjectHmacKeyAllOpt { private static final long serialVersionUID = 3962499996741180460L; private UserProject(String val) { @@ -2004,28 +2034,10 @@ public Mapper getGrpcMetadataMapper() { return ctx -> ctx.withExtraHeaders(ImmutableMap.of("X-Goog-User-Project", ImmutableList.of(val))); } - - @Override - public Mapper rewriteObject() { - return Mapper.identity(); - } - - @Override - public Mapper moveObject() { - return Mapper.identity(); - } } static final class Headers extends RpcOptVal> - implements BucketSourceOpt, - BucketTargetOpt, - BucketListOpt, - ObjectSourceOpt, - ObjectTargetOpt, - ObjectListOpt, - HmacKeySourceOpt, - HmacKeyTargetOpt, - HmacKeyListOpt { + implements BucketObjectHmacKeyAllOpt { /** * The set of header names which are blocked from being able to be provided for an instance of @@ -2181,16 +2193,6 @@ private void copyEntries( } } } - - @Override - public Mapper rewriteObject() { - return Mapper.identity(); - } - - @Override - public Mapper moveObject() { - return Mapper.identity(); - } } static final class VersionsFilter extends RpcOptVal<@NonNull Boolean> implements ObjectListOpt { @@ -2606,6 +2608,17 @@ public String toString() { } } + static final class DefaultHasherSelector implements HasherSelector, Opt { + private static final DefaultHasherSelector INSTANCE = new DefaultHasherSelector(); + + private DefaultHasherSelector() {} + + @Override + public Hasher getHasher() { + return Hasher.defaultHasher(); + } + } + /** * Internal "collection" class to represent a set of {@link Opt}s, and to provide useful * transformations to individual mappers or to resolve any extractors providing a new instance @@ -2702,6 +2715,22 @@ Opts projectAsSource() { return Utils.mapBuild(builder); } + @VisibleForTesting + HasherSelector getHasherSelector() { + HasherSelector search = defaultHasherSelector(); + Predicate p = isInstanceOf(HasherSelector.class); + for (T opt : opts) { + if (p.test(opt)) { + search = (HasherSelector) opt; + } + } + return search; + } + + Hasher getHasher() { + return getHasherSelector().getHasher(); + } + Mapper grpcMetadataMapper() { return fuseMappers(GrpcMetadataMapper.class, GrpcMetadataMapper::getGrpcMetadataMapper); } @@ -2754,7 +2783,7 @@ Mapper readObjectRequest() { return fuseMappers(ObjectSourceOpt.class, ObjectSourceOpt::readObject); } - public Mapper bidiReadObjectRequest() { + Mapper bidiReadObjectRequest() { return fuseMappers(ObjectSourceOpt.class, ObjectSourceOpt::bidiReadObject); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/UnifiedOptsTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/UnifiedOptsTest.java index 73d88580a4..e5a38b4365 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/UnifiedOptsTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/UnifiedOptsTest.java @@ -20,6 +20,10 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import com.google.cloud.storage.UnifiedOpts.Crc32cMatch; +import com.google.cloud.storage.UnifiedOpts.DefaultHasherSelector; +import com.google.cloud.storage.UnifiedOpts.HasherSelector; +import com.google.cloud.storage.UnifiedOpts.Md5Match; import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt; import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; import com.google.cloud.storage.UnifiedOpts.Opt; @@ -146,6 +150,27 @@ public void validateFactoryMethodEnforceNonNull_storage_updateHmacKeyOption() th validateFactoryMethodEnforceNonNull(Storage.UpdateHmacKeyOption.class); } + @Test + public void getHasher_selectsLastValue() { + DefaultHasherSelector first = UnifiedOpts.defaultHasherSelector(); + Md5Match second = UnifiedOpts.md5Match("asdf"); + Crc32cMatch third = UnifiedOpts.crc32cMatch(3); + Opts hasherOpts = Opts.from(first, second, third); + + HasherSelector actual = hasherOpts.getHasherSelector(); + assertThat(actual).isSameInstanceAs(third); + } + + @Test + public void hasher_md5Match_noop() { + assertThat(UnifiedOpts.md5Match("xyz").getHasher()).isEqualTo(Hasher.noop()); + } + + @Test + public void hasher_crc32cMatch_noop() { + assertThat(UnifiedOpts.crc32cMatch(77).getHasher()).isEqualTo(Hasher.noop()); + } + @Test public void transformTo() { SecretKey key = @@ -172,8 +197,8 @@ public byte[] getEncoded() { UnifiedOpts.encryptionKey(key), // userProject implements both target and source UnifiedOpts.userProject("user-project"), - // crc32c is not a source opt or a ProjectToSource opt, it should be excluded - UnifiedOpts.crc32cMatch(1)); + // contentType is not a source opt or a ProjectToSource opt, it should be excluded + UnifiedOpts.setContentType("application/octet-stream")); Opts sourceOpts = targetOpts.transformTo(ObjectSourceOpt.class); Opts expected =