pubsub: add Subscription.Update; remove Subcription.ModifyPushConfig

BREAKING: ModifyPushConfig is removed.

Added Subcription.Update. Following the model used for GCS buckets,
defined a new type that parallels SubscriptionConfig, but has only
updateable fields, and can distinguish absence from a zero
value for each field.

Currently the push config is the only updateable field, and since
UpdateSubscription is not available to everyone yet, we call
ModifyPushConfig under the hood.

Change-Id: I00e27409ea4904bb4e416cd195883ce00b436f55
Reviewed-on: https://code-review.googlesource.com/14311
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Michael Darakananda <pongad@google.com>
diff --git a/pubsub/example_test.go b/pubsub/example_test.go
index bca34d8..1747080 100644
--- a/pubsub/example_test.go
+++ b/pubsub/example_test.go
@@ -252,14 +252,18 @@
 	}
 }
 
-func ExampleSubscription_ModifyPushConfig() {
+func ExampleSubscription_Update() {
 	ctx := context.Background()
 	client, err := pubsub.NewClient(ctx, "project-id")
 	if err != nil {
 		// TODO: Handle error.
 	}
 	sub := client.Subscription("subName")
-	if err := sub.ModifyPushConfig(ctx, pubsub.PushConfig{Endpoint: "https://example.com/push"}); err != nil {
+	subConfig, err := sub.Update(ctx, pubsub.SubscriptionConfigToUpdate{
+		PushConfig: &pubsub.PushConfig{Endpoint: "https://example.com/push"},
+	})
+	if err != nil {
 		// TODO: Handle error.
 	}
+	_ = subConfig // TODO: Use SubscriptionConfig.
 }
diff --git a/pubsub/integration_test.go b/pubsub/integration_test.go
index 6af82e8..07d11b6 100644
--- a/pubsub/integration_test.go
+++ b/pubsub/integration_test.go
@@ -272,7 +272,7 @@
 	return "", true
 }
 
-func TestModifyPushConfig(t *testing.T) {
+func TestSubscriptionUpdate(t *testing.T) {
 	t.Parallel()
 	ctx := context.Background()
 	if testing.Short() {
@@ -310,15 +310,10 @@
 	}
 	defer sub.Delete(ctx)
 
-	getConfig := func() SubscriptionConfig {
-		sc, err := sub.Config(ctx)
-		if err != nil {
-			t.Fatal(err)
-		}
-		return sc
+	sc, err := sub.Config(ctx)
+	if err != nil {
+		t.Fatal(err)
 	}
-
-	sc := getConfig()
 	if !reflect.DeepEqual(sc.PushConfig, PushConfig{}) {
 		t.Fatalf("got %+v, want empty PushConfig")
 	}
@@ -327,22 +322,30 @@
 		Endpoint:   "https://" + projID + ".appspot.com/_ah/push-handlers/push",
 		Attributes: map[string]string{"x-goog-version": "v1"},
 	}
-	if err := sub.ModifyPushConfig(ctx, pc); err != nil {
+	sc, err = sub.Update(ctx, SubscriptionConfigToUpdate{PushConfig: &pc})
+	if err != nil {
 		t.Fatal(err)
 	}
 	// Despite the docs which say that Get always returns a valid "x-goog-version"
 	// attribute, none is returned. See
 	// https://cloud.google.com/pubsub/docs/reference/rpc/google.pubsub.v1#google.pubsub.v1.PushConfig
 	pc.Attributes = nil
-	if got, want := getConfig().PushConfig, pc; !reflect.DeepEqual(got, want) {
+	if got, want := sc.PushConfig, pc; !reflect.DeepEqual(got, want) {
 		t.Fatalf("setting push config: got\n%+v\nwant\n%+v", got, want)
 	}
 	// Remove the PushConfig, turning the subscription back into pull mode.
 	pc = PushConfig{}
-	if err := sub.ModifyPushConfig(ctx, pc); err != nil {
+	sc, err = sub.Update(ctx, SubscriptionConfigToUpdate{PushConfig: &pc})
+	if err != nil {
 		t.Fatal(err)
 	}
-	if got, want := getConfig().PushConfig, pc; !reflect.DeepEqual(got, want) {
+	if got, want := sc.PushConfig, pc; !reflect.DeepEqual(got, want) {
 		t.Fatalf("removing push config: got\n%+v\nwant %+v", got, want)
 	}
+
+	// If nothing changes, our client returns an error.
+	_, err = sub.Update(ctx, SubscriptionConfigToUpdate{})
+	if err == nil {
+		t.Fatal("got nil, wanted error")
+	}
 }
diff --git a/pubsub/subscription.go b/pubsub/subscription.go
index 3534f45..6b8c3af 100644
--- a/pubsub/subscription.go
+++ b/pubsub/subscription.go
@@ -187,9 +187,24 @@
 	return conf, nil
 }
 
-// ModifyPushConfig updates the endpoint URL and other attributes of a push subscription.
-func (s *Subscription) ModifyPushConfig(ctx context.Context, conf PushConfig) error {
-	return s.s.modifyPushConfig(ctx, s.name, conf)
+// SubscriptionConfigToUpdate describes how to update a subscription.
+type SubscriptionConfigToUpdate struct {
+	// If non-nil, the push config is changed.
+	PushConfig *PushConfig
+}
+
+// Update changes an existing subscription according to the fields set in cfg.
+// It returns the new SubscriptionConfig.
+//
+// Update returns an error if no fields were modified.
+func (s *Subscription) Update(ctx context.Context, cfg SubscriptionConfigToUpdate) (SubscriptionConfig, error) {
+	if cfg.PushConfig == nil {
+		return SubscriptionConfig{}, errors.New("pubsub: UpdateSubscription call with nothing to update")
+	}
+	if err := s.s.modifyPushConfig(ctx, s.name, *cfg.PushConfig); err != nil {
+		return SubscriptionConfig{}, err
+	}
+	return s.Config(ctx)
 }
 
 func (s *Subscription) IAM() *iam.Handle {