Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update CloudEvents SDK to support most recent spec change to extensions #2

Merged
merged 2 commits into from
Oct 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ Package cloudevents provides primitives to work with CloudEvents specification:
Parsing Event from HTTP Request:
```go
import "github.com/dispatchframework/cloudevents-go-sdk"
marshaller := v01.NewDefaultHTTPMarshaller()
// req is *http.Request
event, err := cloudEvents.FromHTTPRequest(req)
event, err := marshaller.FromRequest(req)
if err != nil {
panic("Unable to parse event from http Request: " + err.String())
}
fmt.Printf("eventType: %s", event.EventType)
fmt.Printf("eventType: %s", event.Get("eventType")
```

Creating a minimal CloudEvent in version 0.1:
Expand All @@ -27,8 +28,9 @@ import "github.com/dispatchframework/cloudevents-go-sdk/v01"

Creating HTTP request from CloudEvent:
```
marshaller := v01.NewDefaultHTTPMarshaller()
var req *http.Request
err := event.ToHTTPRequest(req)
err := event.ToRequest(req)
if err != nil {
panic("Unable to marshal event into http Request: " + err.String())
}
Expand Down
7 changes: 7 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ type ContentTypeNotSupportedError string
func (e ContentTypeNotSupportedError) Error() string {
return "provided content type " + string(e) + " is not supported"
}

// IllegalArgumentError is returned when an argument passed to a method is an illegal value
type IllegalArgumentError string

func (e IllegalArgumentError) Error() string {
return "argument " + string(e) + "is illegal"
}
20 changes: 12 additions & 8 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package cloudevents

import (
"net/http"

"github.com/dispatchframework/cloudevents-go-sdk/v01"
"reflect"
)

// FromHTTPRequest parses a CloudEvent from any known encoding.
func FromHTTPRequest(req *http.Request) (Event, error) {
// TODO: this should check the version of incoming CloudVersion header and create an appropriate event structure.
e := &v01.Event{}
err := e.FromHTTPRequest(req)
return e, err
// HTTPMarshaller an interface with methods for creating CloudEvents
type HTTPMarshaller interface {
FromRequest(req *http.Request) (Event, error)
ToRequest(req *http.Request, event Event) error
}

// HTTPCloudEventConverter an interface for defining converters that can read/write CloudEvents from HTTP requests
type HTTPCloudEventConverter interface {
CanRead(t reflect.Type, mediaType string) bool
CanWrite(t reflect.Type, mediaType string) bool
Read(t reflect.Type, req *http.Request) (Event, error)
Write(t reflect.Type, req *http.Request, event Event) error
}
77 changes: 77 additions & 0 deletions mocks/http_cloud_event_converter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

190 changes: 190 additions & 0 deletions v01/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package v01

import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"reflect"

"github.com/dispatchframework/cloudevents-go-sdk"
)

// HTTPMarshaller A struct representing the v01 version of the HTTPMarshaller
type HTTPMarshaller struct {
converters []cloudevents.HTTPCloudEventConverter
}

// NewDefaultHTTPMarshaller creates a new v01 HTTPMarshaller prepopulated with the Binary and JSON
// CloudEvent converters
func NewDefaultHTTPMarshaller() cloudevents.HTTPMarshaller {
return NewHTTPMarshaller(
NewJSONHTTPCloudEventConverter(),
NewBinaryHTTPCloudEventConverter())
}

// NewHTTPMarshaller creates a new HTTPMarshaller with the given HTTPCloudEventConverters
func NewHTTPMarshaller(converters ...cloudevents.HTTPCloudEventConverter) cloudevents.HTTPMarshaller {
return &HTTPMarshaller{
converters: converters,
}
}

// FromRequest creates a new CloudEvent from an http Request
func (e HTTPMarshaller) FromRequest(req *http.Request) (cloudevents.Event, error) {
if req == nil {
return nil, cloudevents.IllegalArgumentError("req")
}

mimeType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
if err != nil {
return nil, fmt.Errorf("error parsing request content type: %s", err.Error())
}

for _, v := range e.converters {
if v.CanRead(reflect.TypeOf(Event{}), mimeType) {
return v.Read(reflect.TypeOf(Event{}), req)
}
}
return nil, cloudevents.ContentTypeNotSupportedError(mimeType)
}

// ToRequest populates an http Request with the given CloudEvent
func (e HTTPMarshaller) ToRequest(req *http.Request, event cloudevents.Event) error {
if req == nil {
return cloudevents.IllegalArgumentError("req")
}

if event == nil {
return cloudevents.IllegalArgumentError("event")
}

v01Event := event.(*Event)

contentType := v01Event.ContentType
if contentType == "" {
contentType = "application/cloudevents+json"
}

mimeType, _, err := mime.ParseMediaType(contentType)
if err != nil {
return fmt.Errorf("error parsing event content type: %s", err.Error())
}

for _, v := range e.converters {
if v.CanWrite(reflect.TypeOf(Event{}), mimeType) {
err := v.Write(reflect.TypeOf(Event{}), req, event)
if err != nil {
return err
}

return nil
}
}

return cloudevents.ContentTypeNotSupportedError(mimeType)
}

// jsonhttpCloudEventConverter new converter for reading/writing CloudEvents to JSON
type jsonhttpCloudEventConverter struct {
supportedMediaTypes map[string]bool
supportedMediaTypesSlice []string
}

// NewJSONHTTPCloudEventConverter creates a new JSONHTTPCloudEventConverter
func NewJSONHTTPCloudEventConverter() cloudevents.HTTPCloudEventConverter {
mediaTypes := map[string]bool{
"application/cloudevents+json": true,
}

return &jsonhttpCloudEventConverter{
supportedMediaTypes: mediaTypes,
}
}

// CanRead specifies if this converter can read the given mediaType into a given reflect.Type
func (j *jsonhttpCloudEventConverter) CanRead(t reflect.Type, mediaType string) bool {
return reflect.TypeOf(Event{}) == t && j.supportedMediaTypes[mediaType]
}

// CanWrite specifies if this converter can write the given Type into the given mediaType
func (j *jsonhttpCloudEventConverter) CanWrite(t reflect.Type, mediaType string) bool {
return reflect.TypeOf(Event{}) == t && j.supportedMediaTypes[mediaType]
}

func (j *jsonhttpCloudEventConverter) Read(t reflect.Type, req *http.Request) (cloudevents.Event, error) {
e := reflect.New(t).Interface()
err := json.NewDecoder(req.Body).Decode(e)

if err != nil {
return nil, fmt.Errorf("error parsing request: %s", err.Error())
}

ret := e.(*Event)
return ret, nil
}

func (j *jsonhttpCloudEventConverter) Write(t reflect.Type, req *http.Request, event cloudevents.Event) error {
buffer := bytes.Buffer{}
if err := json.NewEncoder(&buffer).Encode(event); err != nil {
return err
}

req.Body = ioutil.NopCloser(&buffer)
req.ContentLength = int64(buffer.Len())
req.GetBody = func() (io.ReadCloser, error) {
reader := bytes.NewReader(buffer.Bytes())
return ioutil.NopCloser(reader), nil
}

req.Header.Set("Content-Type", "application/cloudevents+json")
return nil
}

// BinaryHTTPCloudEventConverter a converter for reading/writing CloudEvents into the binary format
type binaryHTTPCloudEventConverter struct {
supportedMediaTypes map[string]bool
}

// NewBinaryHTTPCloudEventConverter creates a new BinaryHTTPCloudEventConverter
func NewBinaryHTTPCloudEventConverter() cloudevents.HTTPCloudEventConverter {
mediaTypes := map[string]bool{
"application/json": true,
"application/xml": true,
"application/octet-stream": true,
}

return &binaryHTTPCloudEventConverter{
supportedMediaTypes: mediaTypes,
}
}

// CanRead specifies if this converter can read the given mediaType into a given reflect.Type
func (b *binaryHTTPCloudEventConverter) CanRead(t reflect.Type, mediaType string) bool {
return reflect.TypeOf(Event{}) == t && b.supportedMediaTypes[mediaType]
}

// CanWrite specifies if this converter can write the given Type into the given mediaType
func (b *binaryHTTPCloudEventConverter) CanWrite(t reflect.Type, mediaType string) bool {
return reflect.TypeOf(Event{}) == t && b.supportedMediaTypes[mediaType]
}

func (b *binaryHTTPCloudEventConverter) Read(t reflect.Type, req *http.Request) (cloudevents.Event, error) {
var event Event
if err := event.UnmarshalBinary(req); err != nil {
return nil, err
}

return &event, nil
}

func (b *binaryHTTPCloudEventConverter) Write(t reflect.Type, req *http.Request, event cloudevents.Event) error {
e := event.(*Event)
if err := e.MarshalBinary(req); err != nil {
return err
}
return nil
}
Loading