functions: don't use a wrapper and make Resource a value

We'll switch Resource back to a pointer after the next worker release.

The wrapper doesn't work because the JSON is provided by the worker.
The worker does not include the wrapper. So, we end up with a mismatch:

    with wrapper: {"m":{"eventId":"test event ID","timestamp":"0001-01-01T00:00:00Z","eventType":"","resource":null}}

    no wrapper: {"eventId":"test event ID","timestamp":"0001-01-01T00:00:00Z","eventType":"","resource":null}

Making testing easier is not enough of a reason to add the additional
layer of indirection in the JSON with the wrapper.

Change-Id: Ifcd3df6f82377f8ddacd6621be8e759cf5a2b44c
Reviewed-on: https://code-review.googlesource.com/c/36070
Reviewed-by: Chris Broadfoot <cbro@google.com>
Reviewed-by: Jean de Klerk <deklerk@google.com>
diff --git a/functions/metadata/metadata.go b/functions/metadata/metadata.go
index 6f4120d..26f4434 100644
--- a/functions/metadata/metadata.go
+++ b/functions/metadata/metadata.go
@@ -31,7 +31,8 @@
 	// EventType is the type of the event. For example: "google.pubsub.topic.publish".
 	EventType string `json:"eventType"`
 	// Resource is the resource that triggered the event.
-	Resource *Resource `json:"resource"`
+	// TODO(tbp): Make this a pointer.
+	Resource Resource `json:"resource"`
 }
 
 // Resource holds Google Cloud Functions resource metadata.
@@ -45,15 +46,12 @@
 	Type string `json:"type"`
 }
 
-// wrapper wraps Metadata to make nil serialization work nicely.
-type wrapper struct {
-	M *Metadata `json:"m,omitempty"`
-}
-
 type contextKey string
 
 // GCFContextKey satisfies an interface to be able to use contextKey to read
 // metadata from a Cloud Functions context.Context.
+//
+// Be careful making changes to this function. See FromContext.
 func (k contextKey) GCFContextKey() string {
 	return string(k)
 }
@@ -65,21 +63,34 @@
 	if ctx == nil {
 		return nil, errors.New("nil ctx")
 	}
+	// The original JSON is inserted by the Cloud Functions worker. So, the
+	// format must not change, or the message may fail to unmarshal. We use
+	// JSON as a common format between the worker and this package to ensure
+	// this package can be updated independently from the worker. The contextKey
+	// type and the metadataContextKey value use an interface to avoid using
+	// a built-in type as a context key (which is easy to have collisions with).
+	// If we need another value to be stored in the context, we can use a new
+	// key or interface and avoid needing to change this one. Similarly, if we
+	// need to change the format of the message, we should add an additional key
+	// to keep backward compatibility.
 	b, ok := ctx.Value(metadataContextKey).(json.RawMessage)
 	if !ok {
 		return nil, errors.New("unable to find metadata")
 	}
-	w := &wrapper{}
-	if err := json.Unmarshal(b, w); err != nil {
+	meta := &Metadata{}
+	if err := json.Unmarshal(b, meta); err != nil {
 		return nil, fmt.Errorf("json.Unmarshal: %v", err)
 	}
-	return w.M, nil
+	return meta, nil
 }
 
-// NewContext returns a new Context carrying m. NewContext is useful for
-// writing tests which rely on Metadata.
+// NewContext returns a new Context carrying m. If m is nil, NewContext returns
+// ctx. NewContext is only used for writing tests which rely on Metadata.
 func NewContext(ctx context.Context, m *Metadata) context.Context {
-	b, err := json.Marshal(&wrapper{M: m})
+	if m == nil {
+		return ctx
+	}
+	b, err := json.Marshal(m)
 	if err != nil {
 		return ctx
 	}
diff --git a/functions/metadata/metadata_test.go b/functions/metadata/metadata_test.go
index 065c379..9f9d2e5 100644
--- a/functions/metadata/metadata_test.go
+++ b/functions/metadata/metadata_test.go
@@ -27,7 +27,6 @@
 		{
 			&Metadata{EventID: "test event ID"},
 		},
-		{},
 	}
 	for _, test := range tests {
 		ctx := NewContext(context.Background(), test.meta)
@@ -48,4 +47,7 @@
 	if _, err := FromContext(context.Background()); err == nil {
 		t.Errorf("FromContext got no error, wanted an error")
 	}
+	if _, err := FromContext(NewContext(context.Background(), nil)); err == nil {
+		t.Errorf("FromContext got no error, wanted an error")
+	}
 }