diff --git a/pubsublite/integration_test.go b/pubsublite/integration_test.go index 80076f59a01..53aff1ba399 100644 --- a/pubsublite/integration_test.go +++ b/pubsublite/integration_test.go @@ -130,7 +130,7 @@ func TestIntegration_ResourceAdminOperations(t *testing.T) { region, _ := wire.ZoneToRegion(zone) resourceID := resourceIDs.New() - locationPath := wire.LocationPath{Project: proj, Zone: zone}.String() + locationPath := wire.LocationPath{Project: proj, Location: zone}.String() topicPath := wire.TopicPath{Project: proj, Zone: zone, TopicID: resourceID}.String() subscriptionPath := wire.SubscriptionPath{Project: proj, Zone: zone, SubscriptionID: resourceID}.String() t.Logf("Topic path: %s", topicPath) diff --git a/pubsublite/internal/wire/resources.go b/pubsublite/internal/wire/resources.go index 228d632235b..f76bf5cedaf 100644 --- a/pubsublite/internal/wire/resources.go +++ b/pubsublite/internal/wire/resources.go @@ -49,18 +49,18 @@ func ZoneToRegion(zone string) (string, error) { return zone[0:strings.LastIndex(zone, "-")], nil } -// LocationPath stores a path consisting of a project and zone. +// LocationPath stores a path consisting of a project and zone/region. type LocationPath struct { // A Google Cloud project. The project ID (e.g. "my-project") or the project // number (e.g. "987654321") can be provided. Project string - // A Google Cloud zone, for example "us-central1-a". - Zone string + // A Google Cloud zone or region, for example "us-central1-a", "us-central1". + Location string } func (l LocationPath) String() string { - return fmt.Sprintf("projects/%s/locations/%s", l.Project, l.Zone) + return fmt.Sprintf("projects/%s/locations/%s", l.Project, l.Location) } var locPathRE = regexp.MustCompile(`^projects/([^/]+)/locations/([^/]+)$`) @@ -72,7 +72,7 @@ func ParseLocationPath(input string) (LocationPath, error) { return LocationPath{}, fmt.Errorf("pubsublite: invalid location path %q. valid format is %q", input, "projects/PROJECT_ID/locations/ZONE") } - return LocationPath{Project: parts[1], Zone: parts[2]}, nil + return LocationPath{Project: parts[1], Location: parts[2]}, nil } // TopicPath stores the full path of a Pub/Sub Lite topic. @@ -94,7 +94,7 @@ func (t TopicPath) String() string { // Location returns the topic's location path. func (t TopicPath) Location() LocationPath { - return LocationPath{Project: t.Project, Zone: t.Zone} + return LocationPath{Project: t.Project, Location: t.Zone} } var topicPathRE = regexp.MustCompile(`^projects/([^/]+)/locations/([^/]+)/topics/([^/]+)$`) @@ -129,7 +129,7 @@ func (s SubscriptionPath) String() string { // Location returns the subscription's location path. func (s SubscriptionPath) Location() LocationPath { - return LocationPath{Project: s.Project, Zone: s.Zone} + return LocationPath{Project: s.Project, Location: s.Zone} } var subsPathRE = regexp.MustCompile(`^projects/([^/]+)/locations/([^/]+)/subscriptions/([^/]+)$`) @@ -144,6 +144,40 @@ func ParseSubscriptionPath(input string) (SubscriptionPath, error) { return SubscriptionPath{Project: parts[1], Zone: parts[2], SubscriptionID: parts[3]}, nil } +// ReservationPath stores the full path of a Pub/Sub Lite reservation. +type ReservationPath struct { + // A Google Cloud project. The project ID (e.g. "my-project") or the project + // number (e.g. "987654321") can be provided. + Project string + + // A Google Cloud region. An example region is "us-central1". + Region string + + // The ID of the Pub/Sub Lite reservation, for example "my-reservation-name". + ReservationID string +} + +func (r ReservationPath) String() string { + return fmt.Sprintf("projects/%s/locations/%s/reservations/%s", r.Project, r.Region, r.ReservationID) +} + +// Location returns the reservation's location path. +func (r ReservationPath) Location() LocationPath { + return LocationPath{Project: r.Project, Location: r.Region} +} + +var reservationPathRE = regexp.MustCompile(`^projects/([^/]+)/locations/([^/]+)/reservations/([^/]+)$`) + +// ParseReservationPath parses the full path of a Pub/Sub Lite reservation. +func ParseReservationPath(input string) (ReservationPath, error) { + parts := reservationPathRE.FindStringSubmatch(input) + if len(parts) < 4 { + return ReservationPath{}, fmt.Errorf("pubsublite: invalid reservation path %q. valid format is %q", + input, "projects/PROJECT_ID/locations/REGION/reservations/RESERVATION_ID") + } + return ReservationPath{Project: parts[1], Region: parts[2], ReservationID: parts[3]}, nil +} + type topicPartition struct { Path string Partition int diff --git a/pubsublite/internal/wire/resources_test.go b/pubsublite/internal/wire/resources_test.go index 40582ebbde8..45772fbcaf6 100644 --- a/pubsublite/internal/wire/resources_test.go +++ b/pubsublite/internal/wire/resources_test.go @@ -120,7 +120,7 @@ func TestParseLocationPath(t *testing.T) { { desc: "valid: location path", input: "projects/987654321/locations/europe-west1-d", - wantPath: LocationPath{Project: "987654321", Zone: "europe-west1-d"}, + wantPath: LocationPath{Project: "987654321", Location: "europe-west1-d"}, }, { desc: "invalid: zone", @@ -270,3 +270,60 @@ func TestParseSubscriptionPath(t *testing.T) { }) } } + +func TestParseReservationPath(t *testing.T) { + for _, tc := range []struct { + desc string + input string + wantPath ReservationPath + wantErr bool + }{ + { + desc: "valid: reservation path", + input: "projects/987654321/locations/europe-west1/reservations/my-reservation", + wantPath: ReservationPath{Project: "987654321", Region: "europe-west1", ReservationID: "my-reservation"}, + }, + { + desc: "invalid: region only", + input: "europe-west1", + wantErr: true, + }, + { + desc: "invalid: topic path", + input: "projects/987654321/locations/europe-west1-d/topics/my-topic", + wantErr: true, + }, + { + desc: "invalid: missing project", + input: "projects//locations/europe-west1/reservations/my-reservation", + wantErr: true, + }, + { + desc: "invalid: missing region", + input: "projects/987654321/locations//reservations/my-reservation", + wantErr: true, + }, + { + desc: "invalid: missing reservation id", + input: "projects/987654321/locations/europe-west1/reservations/", + wantErr: true, + }, + { + desc: "invalid: has prefix", + input: "prefix/projects/987654321/locations/europe-west1/reservations/my-reservation", + wantErr: true, + }, + { + desc: "invalid: has suffix", + input: "projects/my-project/locations/us-west1/reservations/my-reservation/subresource/desc", + wantErr: true, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + gotPath, gotErr := ParseReservationPath(tc.input) + if gotPath != tc.wantPath || (gotErr != nil) != tc.wantErr { + t.Errorf("ParseReservationPath(%q) = (%v, %v), want (%v, err=%v)", tc.input, gotPath, gotErr, tc.wantPath, tc.wantErr) + } + }) + } +}