functions/metadata: handle string resources
Some legacy events arrive with the resource field of the metadata as a
string value in the JSON, rather than the struct. Handle these appropriately
in a custom JSON unmarshaller.
Change-Id: I5634ee6be8ee80d73a93cd8d79b3a3adbe588716
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/48050
Reviewed-by: Tyler Bui-Palsulich <tbp@google.com>
diff --git a/functions/metadata/metadata.go b/functions/metadata/metadata.go
index a5dfd32..1a08f39 100644
--- a/functions/metadata/metadata.go
+++ b/functions/metadata/metadata.go
@@ -43,6 +43,38 @@
Name string `json:"name"`
// Type is the type of event.
Type string `json:"type"`
+ // Path is the path to the resource type (deprecated).
+ // This is the case for some deprecated GCS
+ // notifications, which populate the resource field as a string containing the topic
+ // rather than as the expected dictionary.
+ // See the Attributes section of https://cloud.google.com/storage/docs/pubsub-notifications
+ // for more details.
+ RawPath string `json:"-"`
+}
+
+// UnmarshalJSON specializes the Resource unmarshalling to handle the case where the
+// value is a string instead of a map. See the comment above on RawPath for why this
+// needs to be handled.
+func (r *Resource) UnmarshalJSON(data []byte) error {
+ // Try to unmarshal the resource into a string.
+ var path string
+ if err := json.Unmarshal(data, &path); err == nil {
+ r.RawPath = path
+ return nil
+ }
+
+ // Otherwise, accept whatever the result of the normal unmarshal would be.
+ // Need to define a new type, otherwise it infinitely recurses and panics.
+ type resource Resource
+ var res resource
+ if err := json.Unmarshal(data, &res); err != nil {
+ return err
+ }
+
+ r.Service = res.Service
+ r.Name = res.Name
+ r.Type = res.Type
+ return nil
}
type contextKey string
diff --git a/functions/metadata/metadata_test.go b/functions/metadata/metadata_test.go
index 04622ca..f6c77f4 100644
--- a/functions/metadata/metadata_test.go
+++ b/functions/metadata/metadata_test.go
@@ -16,8 +16,12 @@
import (
"context"
+ "encoding/json"
"reflect"
"testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
)
func TestMetadata(t *testing.T) {
@@ -43,3 +47,76 @@
t.Errorf("FromContext got no error, wanted an error")
}
}
+
+func TestUnmarshalJSON(t *testing.T) {
+ ts, err := time.Parse("2006-01-02T15:04:05Z07:00", "2019-11-04T23:01:10.112Z")
+ if err != nil {
+ t.Fatalf("Error parsing time: %v.", err)
+ }
+ var tests = []struct {
+ name string
+ data []byte
+ want Metadata
+ }{
+ {
+ name: "MetadataWithResource",
+ data: []byte(`{
+ "eventId": "1234567",
+ "timestamp": "2019-11-04T23:01:10.112Z",
+ "eventType": "google.pubsub.topic.publish",
+ "resource": {
+ "service": "pubsub.googleapis.com",
+ "name": "mytopic",
+ "type": "type.googleapis.com/google.pubsub.v1.PubsubMessage"
+ },
+ "data": {
+ "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage",
+ "attributes": null,
+ "data": "test data"
+ }
+ }`),
+ want: Metadata{
+ EventID: "1234567",
+ Timestamp: ts,
+ EventType: "google.pubsub.topic.publish",
+ Resource: &Resource{
+ Service: "pubsub.googleapis.com",
+ Name: "mytopic",
+ Type: "type.googleapis.com/google.pubsub.v1.PubsubMessage",
+ },
+ },
+ },
+ {
+ name: "MetadataWithString",
+ data: []byte(`{
+ "eventId": "1234567",
+ "timestamp": "2019-11-04T23:01:10.112Z",
+ "eventType": "google.pubsub.topic.publish",
+ "resource": "projects/myproject/mytopic",
+ "data": {
+ "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage",
+ "attributes": null,
+ "data": "test data"
+ }
+ }`),
+ want: Metadata{
+ EventID: "1234567",
+ Timestamp: ts,
+ EventType: "google.pubsub.topic.publish",
+ Resource: &Resource{
+ RawPath: "projects/myproject/mytopic",
+ },
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ var m Metadata
+ if err := json.Unmarshal(tc.data, &m); err != nil {
+ t.Errorf("UnmarshalJSON(%s) error: %v", tc.name, err)
+ }
+ if !cmp.Equal(m, tc.want) {
+ t.Errorf("UnmarshalJSON(%s) error: got %v, want %v", tc.name, m, tc.want)
+ }
+ }
+}