Repository: googleapis/java-storage
Pull request #1431 (2022-06-02 20:55:51)

feat: BucketInfo conversion for gRPC
Introducing more support for BucketInfo conversions


Patch:
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index bb7aaed3..65c52a7c 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -92,6 +92,10 @@
       <groupId>com.google.protobuf</groupId>
       <artifactId>protobuf-java-util</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.api.grpc</groupId>
+      <artifactId>proto-google-common-protos</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.threeten</groupId>
       <artifactId>threetenbp</artifactId>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
index a8249643..dc0eaad2 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
@@ -16,6 +16,7 @@
 
 package com.google.cloud.storage;
 
+import static com.google.cloud.storage.Utils.dateTimeCodec;
 import static com.google.cloud.storage.Utils.durationMillisCodec;
 import static com.google.cloud.storage.Utils.ifNonNull;
 import static com.google.cloud.storage.Utils.lift;
@@ -68,22 +69,16 @@ import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
 import com.google.cloud.storage.HmacKey.HmacKeyState;
 import com.google.cloud.storage.NotificationInfo.EventType;
 import com.google.cloud.storage.NotificationInfo.PayloadFormat;
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import java.math.BigInteger;
 import java.time.Duration;
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 
@@ -128,29 +123,6 @@ final class ApiaryConversions {
   private final Codec<NotificationInfo, com.google.api.services.storage.model.Notification>
       notificationInfoCodec = Codec.of(this::notificationEncode, this::notificationDecode);
 
-  @VisibleForTesting
-  final Codec<OffsetDateTime, DateTime> dateTimeCodec =
-      Codec.of(
-          odt -> {
-            ZoneOffset offset = odt.getOffset();
-            int i = Math.toIntExact(TimeUnit.SECONDS.toMinutes(offset.getTotalSeconds()));
-            return new DateTime(odt.toInstant().toEpochMilli(), i);
-          },
-          dt -> {
-            long milli = dt.getValue();
-            int timeZoneShiftMinutes = dt.getTimeZoneShift();
-
-            Duration timeZoneShift = Duration.of(timeZoneShiftMinutes, ChronoUnit.MINUTES);
-
-            int hours = Math.toIntExact(timeZoneShift.toHours());
-            int minutes =
-                Math.toIntExact(
-                    timeZoneShift.minusHours(timeZoneShift.toHours()).getSeconds() / 60);
-            ZoneOffset offset = ZoneOffset.ofHoursMinutes(hours, minutes);
-
-            return Instant.ofEpochMilli(milli).atOffset(offset);
-          });
-
   private ApiaryConversions() {}
 
   Codec<Entity, String> entity() {
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
index fb30b7d3..a528baec 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
@@ -469,13 +469,13 @@ public class BucketInfo implements Serializable {
     public static class LifecycleCondition implements Serializable {
       private static final long serialVersionUID = -6482314338394768785L;
       private final Integer age;
-      private final DateTime createdBefore;
+      private final OffsetDateTime createdBefore;
       private final Integer numberOfNewerVersions;
       private final Boolean isLive;
       private final List<StorageClass> matchesStorageClass;
       private final Integer daysSinceNoncurrentTime;
-      private final DateTime noncurrentTimeBefore;
-      private final DateTime customTimeBefore;
+      private final OffsetDateTime noncurrentTimeBefore;
+      private final OffsetDateTime customTimeBefore;
       private final Integer daysSinceCustomTime;
 
       private LifecycleCondition(Builder builder) {
@@ -493,13 +493,13 @@ public class BucketInfo implements Serializable {
       public Builder toBuilder() {
         return newBuilder()
             .setAge(this.age)
-            .setCreatedBefore(this.createdBefore)
+            .setCreateBeforeOffsetDateTime(this.createdBefore)
             .setNumberOfNewerVersions(this.numberOfNewerVersions)
             .setIsLive(this.isLive)
             .setMatchesStorageClass(this.matchesStorageClass)
             .setDaysSinceNoncurrentTime(this.daysSinceNoncurrentTime)
-            .setNoncurrentTimeBefore(this.noncurrentTimeBefore)
-            .setCustomTimeBefore(this.customTimeBefore)
+            .setNoncurrentTimeBeforeOffsetDateTime(this.noncurrentTimeBefore)
+            .setCustomTimeBeforeOffsetDateTime(this.customTimeBefore)
             .setDaysSinceCustomTime(this.daysSinceCustomTime);
       }
 
@@ -526,7 +526,17 @@ public class BucketInfo implements Serializable {
         return age;
       }
 
+      /** @deprecated Use {@link #getCreatedBeforeOffsetDateTime()} */
+      @Deprecated
       public DateTime getCreatedBefore() {
+        return Utils.dateTimeCodec.nullable().encode(createdBefore);
+      }
+
+      /**
+       * Returns the date and offset from UTC for this condition. If a time other than 00:00:00.000
+       * is present in the value, GCS will truncate to 00:00:00.000.
+       */
+      public OffsetDateTime getCreatedBeforeOffsetDateTime() {
         return createdBefore;
       }
 
@@ -549,13 +559,37 @@ public class BucketInfo implements Serializable {
 
       /**
        * Returns the date in RFC 3339 format with only the date part (for instance, "2013-01-15").
+       *
+       * @deprecated Use {@link #getNoncurrentTimeBeforeOffsetDateTime()}
        */
+      @Deprecated
       public DateTime getNoncurrentTimeBefore() {
+        return Utils.dateTimeCodec.nullable().encode(noncurrentTimeBefore);
+      }
+
+      /**
+       * Returns the date and offset from UTC for this condition. If a time other than 00:00:00.000
+       * is present in the value, GCS will truncate to 00:00:00.000.
+       */
+      public OffsetDateTime getNoncurrentTimeBeforeOffsetDateTime() {
         return noncurrentTimeBefore;
       }
 
-      /* Returns the date in RFC 3339 format with only the date part (for instance, "2013-01-15").*/
+      /**
+       * Returns the date in RFC 3339 format with only the date part (for instance, "2013-01-15").
+       *
+       * @deprecated Use {@link #getCustomTimeBeforeOffsetDateTime()}
+       */
+      @Deprecated
       public DateTime getCustomTimeBefore() {
+        return Utils.dateTimeCodec.nullable().encode(customTimeBefore);
+      }
+
+      /**
+       * Returns the date and offset from UTC for this condition. If a time other than 00:00:00.000
+       * is present in the value, GCS will truncate to 00:00:00.000.
+       */
+      public OffsetDateTime getCustomTimeBeforeOffsetDateTime() {
         return customTimeBefore;
       }
 
@@ -567,13 +601,13 @@ public class BucketInfo implements Serializable {
       /** Builder for {@code LifecycleCondition}. */
       public static class Builder {
         private Integer age;
-        private DateTime createdBefore;
+        private OffsetDateTime createdBefore;
         private Integer numberOfNewerVersions;
         private Boolean isLive;
         private List<StorageClass> matchesStorageClass;
         private Integer daysSinceNoncurrentTime;
-        private DateTime noncurrentTimeBefore;
-        private DateTime customTimeBefore;
+        private OffsetDateTime noncurrentTimeBefore;
+        private OffsetDateTime customTimeBefore;
         private Integer daysSinceCustomTime;
 
         private Builder() {}
@@ -594,9 +628,23 @@ public class BucketInfo implements Serializable {
          * Sets the date a Blob should be created before for an Action to be executed. Note that
          * only the date will be considered, if the time is specified it will be truncated. This
          * condition is satisfied when an object is created before midnight of the specified date in
-         * UTC. *
+         * UTC.
+         *
+         * @deprecated Use {@link #setCreateBeforeOffsetDateTime(OffsetDateTime)}
          */
+        @Deprecated
         public Builder setCreatedBefore(DateTime createdBefore) {
+          return setCreateBeforeOffsetDateTime(
+              Utils.dateTimeCodec.nullable().decode(createdBefore));
+        }
+
+        /**
+         * Sets the date a Blob should be created before for an Action to be executed. Note that
+         * only the date will be considered, if the time is specified it will be truncated. This
+         * condition is satisfied when an object is created before midnight of the specified date in
+         * UTC.
+         */
+        public Builder setCreateBeforeOffsetDateTime(OffsetDateTime createdBefore) {
           this.createdBefore = createdBefore;
           return this;
         }
@@ -646,8 +694,22 @@ public class BucketInfo implements Serializable {
          * Note that only date part will be considered, if the time is specified it will be
          * truncated. This condition is satisfied when the noncurrent time on an object is before
          * this date. This condition is relevant only for versioned objects.
+         *
+         * @deprecated Use {@link #setNoncurrentTimeBeforeOffsetDateTime(OffsetDateTime)}
          */
+        @Deprecated
         public Builder setNoncurrentTimeBefore(DateTime noncurrentTimeBefore) {
+          return setNoncurrentTimeBeforeOffsetDateTime(
+              Utils.dateTimeCodec.nullable().decode(noncurrentTimeBefore));
+        }
+
+        /**
+         * Sets the date with only the date part (for instance, "2013-01-15"). Note that only date
+         * part will be considered, if the time is specified it will be truncated. This condition is
+         * satisfied when the noncurrent time on an object is before this date. This condition is
+         * relevant only for versioned objects.
+         */
+        public Builder setNoncurrentTimeBeforeOffsetDateTime(OffsetDateTime noncurrentTimeBefore) {
           this.noncurrentTimeBefore = noncurrentTimeBefore;
           return this;
         }
@@ -657,8 +719,21 @@ public class BucketInfo implements Serializable {
          * Note that only date part will be considered, if the time is specified it will be
          * truncated. This condition is satisfied when the custom time on an object is before this
          * date in UTC.
+         *
+         * @deprecated Use {@link #setCustomTimeBeforeOffsetDateTime(OffsetDateTime)}
          */
+        @Deprecated
         public Builder setCustomTimeBefore(DateTime customTimeBefore) {
+          return setCustomTimeBeforeOffsetDateTime(
+              Utils.dateTimeCodec.nullable().decode(customTimeBefore));
+        }
+
+        /**
+         * Sets the date with only the date part (for instance, "2013-01-15"). Note that only date
+         * part will be considered, if the time is specified it will be truncated. This condition is
+         * satisfied when the custom time on an object is before this date in UTC.
+         */
+        public Builder setCustomTimeBeforeOffsetDateTime(OffsetDateTime customTimeBefore) {
           this.customTimeBefore = customTimeBefore;
           return this;
         }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
index 658915e9..1bdacd01 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
@@ -16,8 +16,10 @@
 
 package com.google.cloud.storage;
 
+import static com.google.cloud.storage.Utils.durationMillisCodec;
 import static com.google.cloud.storage.Utils.ifNonNull;
 import static com.google.cloud.storage.Utils.lift;
+import static com.google.cloud.storage.Utils.toImmutableListOf;
 import static com.google.cloud.storage.Utils.todo;
 
 import com.google.cloud.storage.BlobInfo.CustomerEncryption;
@@ -33,9 +35,13 @@ import com.google.storage.v2.HmacKeyMetadata;
 import com.google.storage.v2.Object;
 import com.google.storage.v2.ObjectAccessControl;
 import com.google.storage.v2.ObjectChecksums;
+import com.google.type.Date;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
 
 final class GrpcConversions {
   static final GrpcConversions INSTANCE = new GrpcConversions();
@@ -51,10 +57,12 @@ final class GrpcConversions {
   private final Codec<?, ?> corsCodec = Codec.of(Utils::todo, Utils::todo);
   private final Codec<?, ?> loggingCodec = Codec.of(Utils::todo, Utils::todo);
   private final Codec<?, ?> iamConfigurationCodec = Codec.of(Utils::todo, Utils::todo);
-  private final Codec<?, ?> lifecycleRuleCodec = Codec.of(Utils::todo, Utils::todo);
-  private final Codec<?, ?> deleteRuleCodec = Codec.of(Utils::todo, Utils::todo);
+  private final Codec<BucketInfo.LifecycleRule, Bucket.Lifecycle.Rule> lifecycleRuleCodec =
+      Codec.of(this::lifecycleRuleEncode, this::lifecycleRuleDecode);
+  private final Codec<BucketInfo.DeleteRule, Bucket.Lifecycle.Rule> deleteRuleCodec =
+      Codec.of(this::deleteRuleEncode, this::deleteRuleDecode);
   private final Codec<BucketInfo, Bucket> bucketInfoCodec =
-      Codec.of(Utils::todo, this::bucketInfoDecode);
+      Codec.of(this::bucketInfoEncode, this::bucketInfoDecode);
   private final Codec<CustomerEncryption, com.google.storage.v2.CustomerEncryption>
       customerEncryptionCodec =
           Codec.of(this::customerEncryptionEncode, this::customerEncryptionDecode);
@@ -79,6 +87,22 @@ final class GrpcConversions {
                   .plusNanos(t.getNanos())
                   .atOffset(ZoneOffset.UTC));
 
+  @VisibleForTesting
+  final Codec<OffsetDateTime, Date> odtDateCodec =
+      Codec.of(
+          odt -> {
+            OffsetDateTime utc = odt.withOffsetSameInstant(ZoneOffset.UTC);
+            return Date.newBuilder()
+                .setYear(utc.getYear())
+                .setMonth(utc.getMonthValue())
+                .setDay(utc.getDayOfMonth())
+                .build();
+          },
+          d ->
+              LocalDate.of(d.getYear(), d.getMonth(), d.getDay())
+                  .atStartOfDay()
+                  .atOffset(ZoneOffset.UTC));
+
   private GrpcConversions() {}
 
   Codec<?, ?> entity() {
@@ -117,12 +141,12 @@ final class GrpcConversions {
     return todo();
   }
 
-  Codec<?, ?> lifecycleRule() {
-    return todo();
+  Codec<BucketInfo.LifecycleRule, Bucket.Lifecycle.Rule> lifecycleRule() {
+    return lifecycleRuleCodec;
   }
 
-  Codec<?, ?> deleteRule() {
-    return todo();
+  Codec<BucketInfo.DeleteRule, Bucket.Lifecycle.Rule> deleteRule() {
+    return deleteRuleCodec;
   }
 
   Codec<BucketInfo, Bucket> bucketInfo() {
@@ -147,15 +171,18 @@ final class GrpcConversions {
 
   private BucketInfo bucketInfoDecode(Bucket from) {
     BucketInfo.Builder to = BucketInfo.newBuilder(from.getName());
-    Bucket.RetentionPolicy retentionPolicy = from.getRetentionPolicy();
-    ifNonNull(retentionPolicy, Bucket.RetentionPolicy::getIsLocked, to::setRetentionPolicyIsLocked);
-    ifNonNull(
-        retentionPolicy,
-        lift(Bucket.RetentionPolicy::getRetentionPeriod).andThen(Utils.durationMillisCodec::decode),
-        to::setRetentionPeriodDuration);
-    if (from.hasRetentionPolicy() && retentionPolicy.hasEffectiveTime()) {
-      to.setRetentionEffectiveTimeOffsetDateTime(
-          timestampCodec.decode(retentionPolicy.getEffectiveTime()));
+    to.setGeneratedId(from.getBucketId());
+    if (from.hasRetentionPolicy()) {
+      Bucket.RetentionPolicy retentionPolicy = from.getRetentionPolicy();
+      ifNonNull(retentionPolicy.getIsLocked(), to::setRetentionPolicyIsLocked);
+      ifNonNull(
+          retentionPolicy.getRetentionPeriod(),
+          Utils.durationMillisCodec::decode,
+          to::setRetentionPeriodDuration);
+      ifNonNull(
+          retentionPolicy.getEffectiveTime(),
+          timestampCodec::decode,
+          to::setRetentionEffectiveTimeOffsetDateTime);
     }
     ifNonNull(from.getBucketId(), to::setGeneratedId);
     ifNonNull(from.getLocation(), to::setLocation);
@@ -165,26 +192,279 @@ final class GrpcConversions {
     ifNonNull(from.getCreateTime(), timestampCodec::decode, to::setCreateTimeOffsetDateTime);
     ifNonNull(from.getUpdateTime(), timestampCodec::decode, to::setUpdateTimeOffsetDateTime);
     ifNonNull(from.getEncryption(), Bucket.Encryption::getDefaultKmsKey, to::setDefaultKmsKeyName);
-    ifNonNull(from.getWebsite(), Bucket.Website::getMainPageSuffix, to::setIndexPage);
-    ifNonNull(from.getWebsite(), Bucket.Website::getNotFoundPage, to::setNotFoundPage);
     ifNonNull(from.getRpo(), Rpo::valueOf, to::setRpo);
     ifNonNull(from.getStorageClass(), StorageClass::valueOf, to::setStorageClass);
     ifNonNull(from.getVersioning(), Bucket.Versioning::getEnabled, to::setVersioningEnabled);
     ifNonNull(from.getDefaultEventBasedHold(), to::setDefaultEventBasedHold);
     ifNonNull(from.getLabels(), to::setLabels);
-    ifNonNull(from.getBilling(), Bucket.Billing::getRequesterPays, to::setRequesterPays);
+    if (from.hasWebsite()) {
+      to.setIndexPage(from.getWebsite().getMainPageSuffix());
+      to.setNotFoundPage(from.getWebsite().getNotFoundPage());
+    }
+    ifNonNull(
+        from.getLifecycle(),
+        lift(Bucket.Lifecycle::getRuleList).andThen(toImmutableListOf(lifecycleRule()::decode)),
+        to::setLifecycleRules);
+    // preserve mapping to deprecated property
+    ifNonNull(
+        from.getLifecycle(),
+        lift(Bucket.Lifecycle::getRuleList).andThen(toImmutableListOf(deleteRule()::decode)),
+        to::setDeleteRules);
     // TODO(frankyn): Add logging decoder
     // TODO(frankyn): Add entity decoder
+    // TODO(frankyn): Add Cors decoder support
+    // TODO(frnakyn): Add DefaultObjectAcl decoder support
     // TODO(frankyn): Add lifeycle decoder
     // TODO(frankyn): Add deleteRules decoder
     // TODO(frankyn): Add SelfLink when the field is available
     // TODO(frankyn): Add decoder for iamConfig
-    // TODO(frankyn): Add Etag when support is avialable
+    // TODO(frankyn): Add Etag when support is available
+    return to.build();
+  }
+
+  private Bucket bucketInfoEncode(BucketInfo from) {
+    Bucket.Builder to = Bucket.newBuilder();
+    to.setName(from.getName());
+    to.setBucketId(from.getGeneratedId());
+    if (from.getRetentionPeriodDuration() != null) {
+      Bucket.RetentionPolicy.Builder retentionPolicyBuilder = to.getRetentionPolicyBuilder();
+      ifNonNull(
+          from.getRetentionPeriodDuration(),
+          durationMillisCodec::encode,
+          retentionPolicyBuilder::setRetentionPeriod);
+      ifNonNull(from.retentionPolicyIsLocked(), retentionPolicyBuilder::setIsLocked);
+      if (from.retentionPolicyIsLocked() == Boolean.TRUE) {
+        ifNonNull(
+            from.getRetentionEffectiveTimeOffsetDateTime(),
+            timestampCodec::encode,
+            retentionPolicyBuilder::setEffectiveTime);
+      }
+      to.setRetentionPolicy(retentionPolicyBuilder.build());
+    }
+    ifNonNull(from.getLocation(), to::setLocation);
+    ifNonNull(from.getLocationType(), to::setLocationType);
+    ifNonNull(from.getMetageneration(), to::setMetageneration);
+    Bucket.Billing.Builder billingBuilder = Billing.newBuilder();
+    ifNonNull(from.requesterPays(), billingBuilder::setRequesterPays);
+    to.setBilling(billingBuilder.build());
+    ifNonNull(from.getCreateTimeOffsetDateTime(), timestampCodec::encode, to::setCreateTime);
+    ifNonNull(from.getUpdateTimeOffsetDateTime(), timestampCodec::encode, to::setUpdateTime);
+    Bucket.Encryption.Builder encryptionBuilder = Bucket.Encryption.newBuilder();
+    ifNonNull(from.getDefaultKmsKeyName(), encryptionBuilder::setDefaultKmsKey);
+    to.setEncryption(encryptionBuilder.build());
+    Bucket.Website.Builder websiteBuilder = Bucket.Website.newBuilder();
+    ifNonNull(from.getIndexPage(), websiteBuilder::setMainPageSuffix);
+    ifNonNull(from.getNotFoundPage(), websiteBuilder::setNotFoundPage);
+    to.setWebsite(websiteBuilder.build());
+    ifNonNull(from.getRpo(), Rpo::toString, to::setRpo);
+    ifNonNull(from.getStorageClass(), StorageClass::toString, to::setStorageClass);
+    Bucket.Versioning.Builder versioningBuilder = Bucket.Versioning.newBuilder();
+    ifNonNull(from.versioningEnabled(), versioningBuilder::setEnabled);
+    to.setVersioning(versioningBuilder.build());
+    ifNonNull(from.getDefaultEventBasedHold(), to::setDefaultEventBasedHold);
+    ifNonNull(from.getLabels(), to::putAllLabels);
+    // preserve mapping to deprecated property
+    Bucket.Lifecycle.Builder lifecycleBuilder = Bucket.Lifecycle.newBuilder();
+    ifNonNull(
+        from.getLifecycleRules(),
+        Utils.toImmutableListOf(lifecycleRule()::encode),
+        lifecycleBuilder::addAllRule);
+    /*
+    // Corruption occurs by having deleteRule() values added
+    ifNonNull(
+       from.getDeleteRules(),
+       Utils.toImmutableListOf(deleteRule()::encode),
+       lifecycleBuilder::addAllRule);
+    */
+    to.setLifecycle(lifecycleBuilder.build());
+    // TODO(frankyn): Add logging decoder
+    // TODO(frankyn): Add entity decoder
     // TODO(frankyn): Add Cors decoder support
     // TODO(frnakyn): Add DefaultObjectAcl decoder support
+    // TODO(frankyn): Add lifeycle decoder
+    // TODO(frankyn): Add deleteRules decoder
+    // TODO(frankyn): Add SelfLink when the field is available
+    // TODO(frankyn): Add decoder for iamConfig
+    // TODO(frankyn): Add Etag when support is avialable
     return to.build();
   }
 
+  @SuppressWarnings("deprecation")
+  private Bucket.Lifecycle.Rule deleteRuleEncode(BucketInfo.DeleteRule from) {
+    if (from instanceof BucketInfo.RawDeleteRule) {
+      Bucket.Lifecycle.Rule rule =
+          Bucket.Lifecycle.Rule.newBuilder()
+              .setAction(Bucket.Lifecycle.Rule.Action.newBuilder().setType(from.getType().name()))
+              .build();
+      String msg =
+          "The lifecycle condition "
+              + resolveRuleActionType(rule)
+              + " is not currently supported. Please update to the latest version of google-cloud-java."
+              + " Also, use LifecycleRule rather than the deprecated DeleteRule.";
+      // manually construct a log record, so we maintain class name and method name
+      // from the old implicit values.
+      LogRecord record = new LogRecord(Level.WARNING, msg);
+      record.setSourceClassName(BucketInfo.RawDeleteRule.class.getName());
+      record.setSourceMethodName("populateCondition");
+      BucketInfo.log.log(record);
+      return rule;
+    }
+    Bucket.Lifecycle.Rule.Builder to = Bucket.Lifecycle.Rule.newBuilder();
+    to.setAction(
+        Bucket.Lifecycle.Rule.Action.newBuilder().setType(BucketInfo.DeleteRule.SUPPORTED_ACTION));
+    Bucket.Lifecycle.Rule.Condition.Builder condition =
+        Bucket.Lifecycle.Rule.Condition.newBuilder();
+    if (from instanceof BucketInfo.CreatedBeforeDeleteRule) {
+      BucketInfo.CreatedBeforeDeleteRule r = (BucketInfo.CreatedBeforeDeleteRule) from;
+      if (r.getTime() != null) {
+        condition.setCreatedBefore(
+            Date.newBuilder()
+                .setYear(r.getTime().getYear())
+                .setMonth(r.getTime().getMonthValue())
+                .setDay(r.getTime().getDayOfMonth())
+                .build());
+      }
+    } else if (from instanceof BucketInfo.AgeDeleteRule) {
+      BucketInfo.AgeDeleteRule r = (BucketInfo.AgeDeleteRule) from;
+      condition.setAgeDays(r.getDaysToLive());
+    } else if (from instanceof BucketInfo.NumNewerVersionsDeleteRule) {
+      BucketInfo.NumNewerVersionsDeleteRule r = (BucketInfo.NumNewerVersionsDeleteRule) from;
+      condition.setNumNewerVersions(r.getNumNewerVersions());
+    } else if (from instanceof BucketInfo.IsLiveDeleteRule) {
+      BucketInfo.IsLiveDeleteRule r = (BucketInfo.IsLiveDeleteRule) from;
+      condition.setIsLive(r.isLive());
+    } // else would be RawDeleteRule which is handled above
+    to.setCondition(condition);
+    return to.build();
+  }
+
+  private String resolveRuleActionType(Bucket.Lifecycle.Rule rule) {
+    if (rule != null && rule.getAction() != null) {
+      return rule.getAction().getType();
+    } else {
+      return null;
+    }
+  }
+
+  @SuppressWarnings("deprecation")
+  private BucketInfo.DeleteRule deleteRuleDecode(Bucket.Lifecycle.Rule from) {
+    if (from.getAction() != null
+        && BucketInfo.DeleteRule.SUPPORTED_ACTION.endsWith(resolveRuleActionType(from))) {
+      Bucket.Lifecycle.Rule.Condition condition = from.getCondition();
+      Integer age = condition.getAgeDays();
+      if (age != null) {
+        return new BucketInfo.AgeDeleteRule(age);
+      }
+      Date date = condition.getCreatedBefore();
+      if (date != null) {
+        return new BucketInfo.CreatedBeforeDeleteRule(
+            OffsetDateTime.from(LocalDate.of(date.getYear(), date.getMonth(), date.getDay())));
+      }
+      Integer numNewerVersions = condition.getNumNewerVersions();
+      if (numNewerVersions != null) {
+        return new BucketInfo.NumNewerVersionsDeleteRule(numNewerVersions);
+      }
+      Boolean isLive = condition.getIsLive();
+      if (isLive != null) {
+        return new BucketInfo.IsLiveDeleteRule(isLive);
+      }
+    }
+    return new BucketInfo.RawDeleteRule(
+        new com.google.api.services.storage.model.Bucket.Lifecycle.Rule()
+            .setAction(
+                new com.google.api.services.storage.model.Bucket.Lifecycle.Rule.Action()
+                    .setType(resolveRuleActionType(from))));
+  }
+
+  private Bucket.Lifecycle.Rule lifecycleRuleEncode(BucketInfo.LifecycleRule from) {
+    Bucket.Lifecycle.Rule.Builder to = Bucket.Lifecycle.Rule.newBuilder();
+    to.setAction(ruleActionEncode(from.getLifecycleAction()));
+    to.setCondition(ruleConditionEncode(from.getLifecycleCondition()));
+    return to.build();
+  }
+
+  private Bucket.Lifecycle.Rule.Condition ruleConditionEncode(
+      BucketInfo.LifecycleRule.LifecycleCondition from) {
+    Bucket.Lifecycle.Rule.Condition.Builder to =
+        Bucket.Lifecycle.Rule.Condition.newBuilder()
+            .setAgeDays(from.getAge())
+            .setIsLive(from.getIsLive())
+            .setNumNewerVersions(from.getNumberOfNewerVersions())
+            .setDaysSinceNoncurrentTime(from.getDaysSinceNoncurrentTime())
+            .setDaysSinceCustomTime(from.getDaysSinceCustomTime());
+    ifNonNull(from.getCreatedBeforeOffsetDateTime(), odtDateCodec::encode, to::setCreatedBefore);
+    ifNonNull(
+        from.getNoncurrentTimeBeforeOffsetDateTime(),
+        odtDateCodec::encode,
+        to::setNoncurrentTimeBefore);
+    ifNonNull(
+        from.getCustomTimeBeforeOffsetDateTime(), odtDateCodec::encode, to::setCustomTimeBefore);
+    ifNonNull(
+        from.getMatchesStorageClass(),
+        toImmutableListOf(StorageClass::toString),
+        to::addAllMatchesStorageClass);
+    return to.build();
+  }
+
+  private Bucket.Lifecycle.Rule.Action ruleActionEncode(
+      BucketInfo.LifecycleRule.LifecycleAction from) {
+    Bucket.Lifecycle.Rule.Action.Builder to =
+        Bucket.Lifecycle.Rule.Action.newBuilder().setType(from.getActionType());
+    if (from.getActionType().equals(BucketInfo.LifecycleRule.SetStorageClassLifecycleAction.TYPE)) {
+      to.setStorageClass(
+          ((BucketInfo.LifecycleRule.SetStorageClassLifecycleAction) from)
+              .getStorageClass()
+              .toString());
+    }
+    return to.build();
+  }
+
+  private BucketInfo.LifecycleRule lifecycleRuleDecode(Bucket.Lifecycle.Rule from) {
+    BucketInfo.LifecycleRule.LifecycleAction lifecycleAction;
+
+    Bucket.Lifecycle.Rule.Action action = from.getAction();
+
+    switch (action.getType()) {
+      case BucketInfo.LifecycleRule.DeleteLifecycleAction.TYPE:
+        lifecycleAction = BucketInfo.LifecycleRule.LifecycleAction.newDeleteAction();
+        break;
+      case BucketInfo.LifecycleRule.SetStorageClassLifecycleAction.TYPE:
+        lifecycleAction =
+            BucketInfo.LifecycleRule.LifecycleAction.newSetStorageClassAction(
+                StorageClass.valueOf(action.getStorageClass()));
+        break;
+      default:
+        BucketInfo.log.warning(
+            "The lifecycle action "
+                + action.getType()
+                + " is not supported by this version of the library. "
+                + "Attempting to update with this rule may cause errors. Please "
+                + "update to the latest version of google-cloud-storage.");
+        lifecycleAction =
+            BucketInfo.LifecycleRule.LifecycleAction.newLifecycleAction("Unknown action");
+    }
+
+    Bucket.Lifecycle.Rule.Condition condition = from.getCondition();
+
+    BucketInfo.LifecycleRule.LifecycleCondition.Builder conditionBuilder =
+        BucketInfo.LifecycleRule.LifecycleCondition.newBuilder()
+            .setAge(condition.getAgeDays())
+            .setCreateBeforeOffsetDateTime(odtDateCodec.decode(condition.getCreatedBefore()))
+            .setIsLive(condition.getIsLive())
+            .setNumberOfNewerVersions(condition.getNumNewerVersions())
+            .setDaysSinceNoncurrentTime(condition.getDaysSinceNoncurrentTime())
+            .setNoncurrentTimeBeforeOffsetDateTime(
+                odtDateCodec.decode(condition.getNoncurrentTimeBefore()))
+            .setCustomTimeBeforeOffsetDateTime(odtDateCodec.decode(condition.getCustomTimeBefore()))
+            .setDaysSinceCustomTime(condition.getDaysSinceCustomTime());
+    ifNonNull(
+        condition.getMatchesStorageClassList(),
+        toImmutableListOf(StorageClass::valueOf),
+        conditionBuilder::setMatchesStorageClass);
+
+    return new BucketInfo.LifecycleRule(lifecycleAction, conditionBuilder.build());
+  }
+
   private HmacKeyMetadata hmacKeyMetadataEncode(HmacKey.HmacKeyMetadata from) {
     HmacKeyMetadata.Builder to = HmacKeyMetadata.newBuilder();
     to.setAccessId(from.getAccessId());
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
index 1959a274..ce61095e 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
@@ -16,12 +16,19 @@
 
 package com.google.cloud.storage;
 
+import com.google.api.client.util.DateTime;
 import com.google.api.core.InternalApi;
 import com.google.cloud.storage.Conversions.Codec;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import java.time.Duration;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import javax.annotation.Nullable;
@@ -40,6 +47,29 @@ final class Utils {
   static final Codec<Duration, Long> durationMillisCodec =
       Codec.of(Duration::toMillis, Duration::ofMillis);
 
+  @VisibleForTesting
+  static final Codec<OffsetDateTime, DateTime> dateTimeCodec =
+      Codec.of(
+          odt -> {
+            ZoneOffset offset = odt.getOffset();
+            int i = Math.toIntExact(TimeUnit.SECONDS.toMinutes(offset.getTotalSeconds()));
+            return new DateTime(odt.toInstant().toEpochMilli(), i);
+          },
+          dt -> {
+            long milli = dt.getValue();
+            int timeZoneShiftMinutes = dt.getTimeZoneShift();
+
+            Duration timeZoneShift = Duration.of(timeZoneShiftMinutes, ChronoUnit.MINUTES);
+
+            int hours = Math.toIntExact(timeZoneShift.toHours());
+            int minutes =
+                Math.toIntExact(
+                    timeZoneShift.minusHours(timeZoneShift.toHours()).getSeconds() / 60);
+            ZoneOffset offset = ZoneOffset.ofHoursMinutes(hours, minutes);
+
+            return Instant.ofEpochMilli(milli).atOffset(offset);
+          });
+
   private Utils() {}
 
   /**


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketPropertyTest.java
index ca1382ec..35232247 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketPropertyTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketPropertyTest.java
@@ -16,33 +16,12 @@
 
 package com.google.cloud.storage;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.protobuf.util.Timestamps;
 import com.google.storage.v2.Bucket;
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
 
-final class BucketPropertyTest {
+final class BucketInfoPropertyTest extends BaseConvertablePropertyTest<BucketInfo, Bucket> {
 
-  @Property
-  void allBucketsDecode(@ForAll Bucket b) {
-    BucketInfo decoded = Conversions.grpc().bucketInfo().decode(b);
-    assertThat(decoded.getGeneratedId()).isEqualTo(b.getBucketId());
-    assertThat(decoded.getName()).isEqualTo(b.getName());
-    assertThat(decoded.getMetageneration()).isEqualTo(b.getMetageneration());
-    assertThat(decoded.getStorageClass().toString()).isEqualTo(b.getStorageClass());
-    assertThat(decoded.getLocation()).isEqualTo(b.getLocation());
-    assertThat(decoded.getLocationType()).isEqualTo(b.getLocationType());
-    assertThat(decoded.getCreateTime()).isEqualTo(Timestamps.toMillis(b.getCreateTime()));
-    assertThat(decoded.getUpdateTime()).isEqualTo(Timestamps.toMillis(b.getUpdateTime()));
-    assertThat(decoded.versioningEnabled()).isEqualTo(b.getVersioning().getEnabled());
-    assertThat(decoded.getRpo().toString()).isEqualTo(b.getRpo());
-    assertThat(decoded.requesterPays()).isEqualTo(b.getBilling().getRequesterPays());
-    assertThat(decoded.getDefaultKmsKeyName()).isEqualTo(b.getEncryption().getDefaultKmsKey());
-    if (b.getWebsite() != null) {
-      assertThat(decoded.getIndexPage()).isEqualTo(b.getWebsite().getMainPageSuffix());
-      assertThat(decoded.getNotFoundPage()).isEqualTo(b.getWebsite().getNotFoundPage());
-    }
+  @Override
+  Conversions.Codec<BucketInfo, Bucket> codec() {
+    return Conversions.grpc().bucketInfo();
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java
index 82b8190a..71ab929d 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java
@@ -34,8 +34,7 @@ import net.jqwik.time.api.DateTimes;
 
 final class DateTimeCodecPropertyTest {
 
-  private static final Codec<OffsetDateTime, DateTime> codec =
-      ApiaryConversions.INSTANCE.dateTimeCodec;
+  private static final Codec<OffsetDateTime, DateTime> codec = Utils.dateTimeCodec;
 
   @Example
   void codecShouldRoundTrip_UTC() {
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/BucketArbitraryProvider.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/BucketArbitraryProvider.java
index 4c478172..a09fa567 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/BucketArbitraryProvider.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/BucketArbitraryProvider.java
@@ -19,7 +19,6 @@ package com.google.cloud.storage.jqwik;
 import com.google.storage.v2.Bucket;
 import java.util.Collections;
 import java.util.Set;
-import net.jqwik.api.Arbitraries;
 import net.jqwik.api.Arbitrary;
 import net.jqwik.api.Combinators;
 import net.jqwik.api.Tuple;
@@ -35,17 +34,14 @@ public final class BucketArbitraryProvider implements ArbitraryProvider {
 
   @Override
   public Set<Arbitrary<?>> provideFor(TypeUsage targetType, SubtypeProvider subtypeProvider) {
-    Arbitrary<String> bucketID = Arbitraries.strings().all().ofMinLength(0).ofLength(1024);
-    Arbitrary<String> location = Arbitraries.strings().all().ofMinLength(1).ofMaxLength(1024);
-    Arbitrary<String> locationType = Arbitraries.strings().all().ofMinLength(1).ofMaxLength(1024);
     Arbitrary<Bucket> as =
         Combinators.combine(
                 Combinators.combine(
-                        bucketID,
+                        StorageArbitraries.bucketName(),
                         StorageArbitraries.bucketName(),
                         StorageArbitraries.buckets().storageClass(),
-                        location,
-                        locationType,
+                        StorageArbitraries.buckets().location(),
+                        StorageArbitraries.buckets().locationType(),
                         StorageArbitraries.metageneration(),
                         StorageArbitraries.buckets().versioning(),
                         StorageArbitraries.timestamp())
@@ -57,12 +53,13 @@ public final class BucketArbitraryProvider implements ArbitraryProvider {
                         StorageArbitraries.buckets().rpo(),
                         StorageArbitraries.buckets().billing(),
                         StorageArbitraries.buckets().encryption(),
-                        StorageArbitraries.buckets().retentionPolicy())
+                        StorageArbitraries.buckets().retentionPolicy(),
+                        StorageArbitraries.buckets().lifecycle())
                     .as(Tuple::of))
             .as(
                 (t1, t2) ->
                     Bucket.newBuilder()
-                        .setBucketId(t1.get1())
+                        .setBucketId(t1.get1().get())
                         .setName(t1.get2().get())
                         .setStorageClass(t1.get3())
                         .setLocation(t1.get4())
@@ -77,6 +74,7 @@ public final class BucketArbitraryProvider implements ArbitraryProvider {
                         .setBilling(t2.get5())
                         .setEncryption(t2.get6())
                         .setRetentionPolicy(t2.get7())
+                        .setLifecycle(t2.get8())
                         .build());
     return Collections.singleton(as);
   }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java
index a8ff0e46..842cec59 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java
@@ -28,11 +28,14 @@ import com.google.storage.v2.Bucket.Versioning;
 import com.google.storage.v2.Bucket.Website;
 import com.google.storage.v2.CustomerEncryption;
 import com.google.storage.v2.ObjectChecksums;
+import com.google.type.Date;
 import java.nio.charset.StandardCharsets;
 import java.time.ZoneOffset;
 import net.jqwik.api.Arbitraries;
 import net.jqwik.api.Arbitrary;
 import net.jqwik.api.Combinators;
+import net.jqwik.api.Tuple;
+import net.jqwik.api.arbitraries.ListArbitrary;
 import net.jqwik.api.providers.TypeUsage;
 import net.jqwik.time.api.DateTimes;
 
@@ -49,6 +52,19 @@ public final class StorageArbitraries {
                 Timestamp.newBuilder().setSeconds(odt.toEpochSecond()).setNanos(nanos).build());
   }
 
+  public static Arbitrary<Date> date() {
+    return DateTimes.offsetDateTimes()
+        .offsetBetween(ZoneOffset.UTC, ZoneOffset.UTC)
+        .map(
+            odt -> {
+              return Date.newBuilder()
+                  .setYear(odt.getYear())
+                  .setMonth(odt.getMonthValue())
+                  .setDay(odt.getDayOfMonth())
+                  .build();
+            });
+  }
+
   public static Arbitrary<Boolean> bool() {
     return Arbitraries.defaultFor(TypeUsage.of(Boolean.class));
   }
@@ -122,6 +138,71 @@ public final class StorageArbitraries {
 
     private Buckets() {}
 
+    public Arbitrary<Bucket.Lifecycle.Rule.Action> actions() {
+      return Combinators.combine(Arbitraries.of("Delete", "SetStorageClass"), storageClass())
+          .as(
+              (a, s) -> {
+                Bucket.Lifecycle.Rule.Action.Builder actionBuilder =
+                    Bucket.Lifecycle.Rule.Action.newBuilder();
+                actionBuilder.setType(a);
+                if (a.equals("SetStorageClass")) {
+                  actionBuilder.setStorageClass(s);
+                }
+                return actionBuilder.build();
+              });
+    }
+
+    public Arbitrary<Bucket.Lifecycle.Rule> rule() {
+      Arbitrary<Boolean> conditionIsLive = bool();
+      Arbitrary<Integer> conditionAgeDays = Arbitraries.integers().between(0, 100);
+      Arbitrary<Integer> conditionNumberOfNewVersions = Arbitraries.integers().between(0, 10);
+      Arbitrary<Date> conditionCreatedBeforeTime = date();
+      Arbitrary<Integer> conditionDaysSinceNoncurrentTime = Arbitraries.integers().between(0, 10);
+      Arbitrary<Date> conditionNoncurrentTime = date();
+      Arbitrary<Integer> conditionDaysSinceCustomTime = Arbitraries.integers().between(0, 10);
+      Arbitrary<Date> conditionCustomTime = date();
+      ListArbitrary<String> storageClassMatches = storageClass().list().uniqueElements();
+
+      return Combinators.combine(
+              actions(),
+              Combinators.combine(
+                      conditionIsLive,
+                      conditionAgeDays,
+                      conditionNumberOfNewVersions,
+                      conditionCreatedBeforeTime,
+                      conditionDaysSinceNoncurrentTime,
+                      conditionNoncurrentTime,
+                      conditionDaysSinceCustomTime,
+                      conditionCustomTime)
+                  .as(Tuple::of),
+              storageClassMatches)
+          .as(
+              (a, ct, s) ->
+                  Bucket.Lifecycle.Rule.newBuilder()
+                      .setAction(a)
+                      .setCondition(
+                          Bucket.Lifecycle.Rule.Condition.newBuilder()
+                              .setIsLive(ct.get1())
+                              .setAgeDays(ct.get2())
+                              .setNumNewerVersions(ct.get3())
+                              .setCreatedBefore(ct.get4())
+                              .setDaysSinceNoncurrentTime(ct.get5())
+                              .setNoncurrentTimeBefore(ct.get6())
+                              .setDaysSinceCustomTime(ct.get7())
+                              .setCustomTimeBefore(ct.get8())
+                              .addAllMatchesStorageClass(s)
+                              .build())
+                      .build());
+    }
+
+    public Arbitrary<Bucket.Lifecycle> lifecycle() {
+      return rule()
+          .list()
+          .ofMinSize(0)
+          .ofMaxSize(100)
+          .map((r) -> Bucket.Lifecycle.newBuilder().addAllRule(r).build());
+    }
+
     public Arbitrary<Website> website() {
       Arbitrary<String> indexPage = Arbitraries.strings().all().ofMinLength(1).ofMaxLength(1024);
       Arbitrary<String> notFoundPage = Arbitraries.strings().all().ofMinLength(1).ofMaxLength(1024);
@@ -144,12 +225,15 @@ public final class StorageArbitraries {
     public Arbitrary<Bucket.RetentionPolicy> retentionPolicy() {
       return Combinators.combine(bool(), Arbitraries.longs().greaterOrEqual(0), timestamp())
           .as(
-              (locked, period, effectiveTime) ->
-                  RetentionPolicy.newBuilder()
-                      .setIsLocked(locked)
-                      .setRetentionPeriod(period)
-                      .setEffectiveTime(effectiveTime)
-                      .build());
+              (locked, period, effectiveTime) -> {
+                RetentionPolicy.Builder retentionBuilder = RetentionPolicy.newBuilder();
+                retentionBuilder.setRetentionPeriod(period);
+                retentionBuilder.setIsLocked(locked);
+                if (locked) {
+                  retentionBuilder.setEffectiveTime(effectiveTime);
+                }
+                return retentionBuilder.build();
+              });
     }
 
     public Arbitrary<Bucket.Versioning> versioning() {
@@ -157,13 +241,27 @@ public final class StorageArbitraries {
     }
 
     public Arbitrary<String> storageClass() {
-      // TODO: return each of the real values and edge cases (including invalid values)
-      return Arbitraries.strings().all().ofMinLength(1).ofLength(1024);
+      return Arbitraries.of(
+          "STANDARD",
+          "NEARLINE",
+          "COLDLINE",
+          "ARCHIVE",
+          "MULTI_REGIONAL",
+          "REGIONAL",
+          "DURABLE_REDUCED_AVAILABILITY");
     }
 
     public Arbitrary<String> rpo() {
-      // TODO: return each of the real values and edge cases (including invalid values)
-      return Arbitraries.strings().all().ofMinLength(1).ofLength(1024);
+      return Arbitraries.of("DEFAULT", "ASYNC_TURBO");
+    }
+
+    public Arbitrary<String> location() {
+      return Arbitraries.of(
+          "US", "US-CENTRAL1", "US-EAST1", "EUROPE-CENTRAL2", "SOUTHAMERICA-EAST1");
+    }
+
+    public Arbitrary<String> locationType() {
+      return Arbitraries.of("region", "dual-region", "multi-region");
     }
   }