functions/metadata: use JSON and pointers

Fixes #1208.

Change-Id: I7e636e368600ff0d9da5b7c105d20b3ba4b8fae3
Reviewed-on: https://code-review.googlesource.com/c/35870
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
Reviewed-by: Jean de Klerk <deklerk@google.com>
Reviewed-by: Chris Broadfoot <cbro@google.com>
diff --git a/functions/metadata/doc.go b/functions/metadata/doc.go
new file mode 100644
index 0000000..8f5afef
--- /dev/null
+++ b/functions/metadata/doc.go
@@ -0,0 +1,19 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package metadata provides methods for creating and accessing context.Context
+// objects with Google Cloud Functions metadata.
+//
+// NOTE: This package is in alpha. It is not stable, and is likely to change.
+package metadata // import "cloud.google.com/go/functions/metadata"
diff --git a/functions/metadata/metadata.go b/functions/metadata/metadata.go
index 128d722..6f4120d 100644
--- a/functions/metadata/metadata.go
+++ b/functions/metadata/metadata.go
@@ -12,17 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package metadata provides methods for creating and accessing context.Context objects
-// with Google Cloud Functions metadata.
-package metadata // import "cloud.google.com/go/functions/metadata"
+package metadata
 
 import (
 	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
 	"time"
 )
 
-type contextKey struct{}
-
 // Metadata holds Google Cloud Functions metadata.
 type Metadata struct {
 	// EventID is a unique ID for the event. For example: "70172329041928".
@@ -32,7 +31,7 @@
 	// 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"`
+	Resource *Resource `json:"resource"`
 }
 
 // Resource holds Google Cloud Functions resource metadata.
@@ -46,16 +45,43 @@
 	Type string `json:"type"`
 }
 
-// NewContext returns a new Context carrying m.
-func NewContext(ctx context.Context, m Metadata) context.Context {
-	return context.WithValue(ctx, contextKey{}, m)
+// 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.
+func (k contextKey) GCFContextKey() string {
+	return string(k)
+}
+
+const metadataContextKey = contextKey("metadata")
+
 // FromContext extracts the Metadata from the Context, if present.
-func FromContext(ctx context.Context) (Metadata, bool) {
+func FromContext(ctx context.Context) (*Metadata, error) {
 	if ctx == nil {
-		return Metadata{}, false
+		return nil, errors.New("nil ctx")
 	}
-	m, ok := ctx.Value(contextKey{}).(Metadata)
-	return m, ok
+	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 {
+		return nil, fmt.Errorf("json.Unmarshal: %v", err)
+	}
+	return w.M, nil
+}
+
+// NewContext returns a new Context carrying m. NewContext is useful for
+// writing tests which rely on Metadata.
+func NewContext(ctx context.Context, m *Metadata) context.Context {
+	b, err := json.Marshal(&wrapper{M: m})
+	if err != nil {
+		return ctx
+	}
+	return context.WithValue(ctx, metadataContextKey, json.RawMessage(b))
 }
diff --git a/functions/metadata/metadata_test.go b/functions/metadata/metadata_test.go
index 561649b..065c379 100644
--- a/functions/metadata/metadata_test.go
+++ b/functions/metadata/metadata_test.go
@@ -16,19 +16,36 @@
 
 import (
 	"context"
+	"reflect"
 	"testing"
 )
 
 func TestMetadata(t *testing.T) {
-	meta := Metadata{
-		EventID: "test event ID",
+	tests := []struct {
+		meta *Metadata
+	}{
+		{
+			&Metadata{EventID: "test event ID"},
+		},
+		{},
 	}
-	ctx := NewContext(context.Background(), meta)
-	newMeta, ok := FromContext(ctx)
-	if !ok {
-		t.Fatalf("No context metadata found")
+	for _, test := range tests {
+		ctx := NewContext(context.Background(), test.meta)
+		got, err := FromContext(ctx)
+		if err != nil {
+			t.Fatalf("FromContext error: %v", err)
+		}
+		if !reflect.DeepEqual(got, test.meta) {
+			t.Fatalf("FromContext\nGot %v\nWant %v", got, test.meta)
+		}
 	}
-	if newMeta != meta {
-		t.Fatalf("got %v, want %v", newMeta, meta)
+}
+
+func TestMetadataError(t *testing.T) {
+	if _, err := FromContext(nil); err == nil {
+		t.Errorf("FromContext got no error, wanted an error")
+	}
+	if _, err := FromContext(context.Background()); err == nil {
+		t.Errorf("FromContext got no error, wanted an error")
 	}
 }
diff --git a/internal/kokoro/vet.sh b/internal/kokoro/vet.sh
index bb608f1..f2381e0 100755
--- a/internal/kokoro/vet.sh
+++ b/internal/kokoro/vet.sh
@@ -59,6 +59,7 @@
 staticcheck -ignore '
 *:SA1019
 cloud.google.com/go/firestore/internal/doc-snippets.go:*
+cloud.google.com/go/functions/metadata/metadata_test.go:SA1012
 cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client_test.go:*
 cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/dwarf/frame.go:*
 cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/dwarf/typeunit.go:*