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:*