Repository: cloudevents/sdk-go
Pull request #213 (2019-09-25 19:50:23)

Revert back to interface for extensions.
Signed-off-by: Scott Nichols <nicholss@google.com>

alanconway: LGTM

Patch:
diff --git a/pkg/cloudevents/event_marshal.go b/pkg/cloudevents/event_marshal.go
index bdad34d..4dbc388 100644
--- a/pkg/cloudevents/event_marshal.go
+++ b/pkg/cloudevents/event_marshal.go
@@ -296,7 +296,7 @@ func (e *Event) JsonDecodeV1(body []byte, raw map[string]json.RawMessage) error
 	delete(raw, "data_base64")
 
 	if len(raw) > 0 {
-		extensions := make(map[string]string, len(raw))
+		extensions := make(map[string]interface{}, len(raw))
 		for k, v := range raw {
 			var tmp string
 			if err := json.Unmarshal(v, &tmp); err != nil {
diff --git a/pkg/cloudevents/eventcontext.go b/pkg/cloudevents/eventcontext.go
index 3cd06f1..4564eab 100644
--- a/pkg/cloudevents/eventcontext.go
+++ b/pkg/cloudevents/eventcontext.go
@@ -47,6 +47,11 @@ type EventContextReader interface {
 	//
 	// Extensions use the CloudEvents type system, details in package cloudevents/types.
 	GetExtensions() map[string]interface{}
+
+	// GetExtension returns the extension associated with with the given key.
+	// The given key is case insensitive. If the extension can not be found,
+	// an error will be returned.
+	GetExtension(string) (interface{}, error)
 }
 
 // EventContextWriter are the methods required to be a writer of context
diff --git a/pkg/cloudevents/eventcontext_v01_reader.go b/pkg/cloudevents/eventcontext_v01_reader.go
index b673cda..8d75ea7 100644
--- a/pkg/cloudevents/eventcontext_v01_reader.go
+++ b/pkg/cloudevents/eventcontext_v01_reader.go
@@ -1,6 +1,7 @@
 package cloudevents
 
 import (
+	"fmt"
 	"mime"
 	"time"
 )
@@ -85,6 +86,16 @@ func (ec EventContextV01) DeprecatedGetDataContentEncoding() string {
 	return enc
 }
 
+// GetExtensions implements EventContextReader.GetExtensions
 func (ec EventContextV01) GetExtensions() map[string]interface{} {
 	return ec.Extensions
 }
+
+// GetExtension implements EventContextReader.GetExtension
+func (ec EventContextV01) GetExtension(key string) (interface{}, error) {
+	v, ok := caseInsensitiveSearch(key, ec.Extensions)
+	if !ok {
+		return "", fmt.Errorf("%q not found", key)
+	}
+	return v, nil
+}
diff --git a/pkg/cloudevents/eventcontext_v02_reader.go b/pkg/cloudevents/eventcontext_v02_reader.go
index a9a1285..120cdb8 100644
--- a/pkg/cloudevents/eventcontext_v02_reader.go
+++ b/pkg/cloudevents/eventcontext_v02_reader.go
@@ -1,6 +1,7 @@
 package cloudevents
 
 import (
+	"fmt"
 	"mime"
 	"time"
 )
@@ -85,6 +86,16 @@ func (ec EventContextV02) DeprecatedGetDataContentEncoding() string {
 	return enc
 }
 
+// GetExtensions implements EventContextReader.GetExtensions
 func (ec EventContextV02) GetExtensions() map[string]interface{} {
 	return ec.Extensions
 }
+
+// GetExtension implements EventContextReader.GetExtension
+func (ec EventContextV02) GetExtension(key string) (interface{}, error) {
+	v, ok := caseInsensitiveSearch(key, ec.Extensions)
+	if !ok {
+		return "", fmt.Errorf("%q not found", key)
+	}
+	return v, nil
+}
diff --git a/pkg/cloudevents/eventcontext_v03_reader.go b/pkg/cloudevents/eventcontext_v03_reader.go
index 9aafba4..2b3cc20 100644
--- a/pkg/cloudevents/eventcontext_v03_reader.go
+++ b/pkg/cloudevents/eventcontext_v03_reader.go
@@ -1,6 +1,7 @@
 package cloudevents
 
 import (
+	"fmt"
 	"mime"
 	"time"
 )
@@ -80,6 +81,16 @@ func (ec EventContextV03) DeprecatedGetDataContentEncoding() string {
 	return ""
 }
 
+// GetExtensions implements EventContextReader.GetExtensions
 func (ec EventContextV03) GetExtensions() map[string]interface{} {
 	return ec.Extensions
 }
+
+// GetExtension implements EventContextReader.GetExtension
+func (ec EventContextV03) GetExtension(key string) (interface{}, error) {
+	v, ok := caseInsensitiveSearch(key, ec.Extensions)
+	if !ok {
+		return "", fmt.Errorf("%q not found", key)
+	}
+	return v, nil
+}
diff --git a/pkg/cloudevents/eventcontext_v1_reader.go b/pkg/cloudevents/eventcontext_v1_reader.go
index def8830..e3f329d 100644
--- a/pkg/cloudevents/eventcontext_v1_reader.go
+++ b/pkg/cloudevents/eventcontext_v1_reader.go
@@ -1,6 +1,7 @@
 package cloudevents
 
 import (
+	"fmt"
 	"mime"
 	"time"
 )
@@ -77,6 +78,7 @@ func (ec EventContextV1) DeprecatedGetDataContentEncoding() string {
 	return ""
 }
 
+// GetExtensions implements EventContextReader.GetExtensions
 func (ec EventContextV1) GetExtensions() map[string]interface{} {
 	// For now, convert the extensions of v1.0 to the pre-v1.0 style.
 	ext := make(map[string]interface{}, len(ec.Extensions))
@@ -85,3 +87,12 @@ func (ec EventContextV1) GetExtensions() map[string]interface{} {
 	}
 	return ext
 }
+
+// GetExtension implements EventContextReader.GetExtension
+func (ec EventContextV1) GetExtension(key string) (interface{}, error) {
+	v, ok := caseInsensitiveSearch(key, ec.Extensions)
+	if !ok {
+		return "", fmt.Errorf("%q not found", key)
+	}
+	return v, nil
+}
diff --git a/pkg/cloudevents/extensions.go b/pkg/cloudevents/extensions.go
index 27a4b4d..57278df 100644
--- a/pkg/cloudevents/extensions.go
+++ b/pkg/cloudevents/extensions.go
@@ -1,5 +1,7 @@
 package cloudevents
 
+import "strings"
+
 const (
 	// DataContentEncodingKey is the key to DeprecatedDataContentEncoding for versions that do not support data content encoding
 	// directly.
@@ -11,3 +13,13 @@ const (
 	// SubjectKey is the key to Subject for versions that do not support subject directly.
 	SubjectKey = "subject"
 )
+
+func caseInsensitiveSearch(key string, space map[string]interface{}) (interface{}, bool) {
+	lkey := strings.ToLower(key)
+	for k, v := range space {
+		if strings.EqualFold(lkey, strings.ToLower(k)) {
+			return v, true
+		}
+	}
+	return nil, false
+}
diff --git a/pkg/cloudevents/transport/http/codec_v1.go b/pkg/cloudevents/transport/http/codec_v1.go
index 61aaca7..c808510 100644
--- a/pkg/cloudevents/transport/http/codec_v1.go
+++ b/pkg/cloudevents/transport/http/codec_v1.go
@@ -122,7 +122,11 @@ func (v CodecV1) toHeaders(ec *cloudevents.EventContextV1) (http.Header, error)
 	for k, v := range ec.Extensions {
 		// Per spec, extensions are strings and converted to a list of headers as:
 		// ce-key: value
-		h.Set("ce-"+k, v)
+		val, err := types.StringOf(v)
+		if err != nil {
+			return h, err
+		}
+		h.Set("ce-"+k, val)
 	}
 
 	return h, nil
@@ -196,7 +200,7 @@ func (v CodecV1) fromHeaders(h http.Header) (cloudevents.EventContextV1, error)
 	// At this point, we have deleted all the known headers.
 	// Everything left is assumed to be an extension.
 
-	extensions := make(map[string]string)
+	extensions := make(map[string]interface{})
 	for k, _ := range h {
 		if len(k) > len("ce-") && strings.EqualFold(k[:len("ce-")], "ce-") {
 			ak := strings.ToLower(k[len("ce-"):])


Test patch:
diff --git a/pkg/cloudevents/transport/http/codec_v1_test.go b/pkg/cloudevents/transport/http/codec_v1_test.go
index c527ea6..f851878 100644
--- a/pkg/cloudevents/transport/http/codec_v1_test.go
+++ b/pkg/cloudevents/transport/http/codec_v1_test.go
@@ -59,7 +59,7 @@ func TestCodecV1_Encode(t *testing.T) {
 					DataContentType: cloudevents.StringOfApplicationJSON(),
 					Source:          *source,
 					Subject:         &subject,
-					Extensions: map[string]string{
+					Extensions: map[string]interface{}{
 						"test": "extended",
 					},
 				},
@@ -112,7 +112,7 @@ func TestCodecV1_Encode(t *testing.T) {
 					DataContentType: cloudevents.StringOfApplicationJSON(),
 					Source:          *source,
 					Subject:         &subject,
-					Extensions: map[string]string{
+					Extensions: map[string]interface{}{
 						"test": "extended",
 					},
 				},
@@ -171,7 +171,7 @@ func TestCodecV1_Encode(t *testing.T) {
 					DataContentType: cloudevents.StringOfApplicationJSON(),
 					Source:          *source,
 					Subject:         &subject,
-					Extensions: map[string]string{
+					Extensions: map[string]interface{}{
 						"test": "extended",
 					},
 				}.AsV1(),
@@ -213,7 +213,7 @@ func TestCodecV1_Encode(t *testing.T) {
 					DataContentType: cloudevents.StringOfApplicationJSON(),
 					Source:          *source,
 					Subject:         &subject,
-					Extensions: map[string]string{
+					Extensions: map[string]interface{}{
 						"test": "extended",
 					},
 				}.AsV1(),
@@ -337,7 +337,7 @@ func TestCodecV1_Decode(t *testing.T) {
 					DataContentType: cloudevents.StringOfApplicationJSON(),
 					Source:          *source,
 					Subject:         &subject,
-					Extensions: map[string]string{
+					Extensions: map[string]interface{}{
 						"test": "extended binary",
 					},
 				},
@@ -400,7 +400,7 @@ func TestCodecV1_Decode(t *testing.T) {
 					DataContentType: cloudevents.StringOfApplicationJSON(),
 					Source:          *source,
 					Subject:         &subject,
-					Extensions: map[string]string{
+					Extensions: map[string]interface{}{
 						"test": "extended",
 					},
 				}.AsV1(),
@@ -439,7 +439,7 @@ func TestCodecV1_Decode(t *testing.T) {
 					DataContentType: cloudevents.StringOfApplicationJSON(),
 					Source:          *source,
 					Subject:         &subject,
-					Extensions: map[string]string{
+					Extensions: map[string]interface{}{
 						"test": "extended",
 					},
 				}.AsV1(),