Skip to content

Commit

Permalink
Merge pull request #1 from kars7e/master
Browse files Browse the repository at this point in the history
Initial import of SDK code
  • Loading branch information
kars7e authored Jul 18, 2018
2 parents 9ae6856 + ae859c0 commit 783d5b3
Show file tree
Hide file tree
Showing 12 changed files with 553 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Others
## IDE-specific
*.iml
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,45 @@
# cloudevents-go-sdk
Go SDK for CloudEvents (https://github.com/cloudevents/spec)
# Go SDK for [CloudEvents](https://github.com/cloudevents/spec)

**NOTE: This SDK is still considered work in progress, things might (and will) break with every update.**

Package cloudevents provides primitives to work with CloudEvents specification: https://github.com/cloudevents/spec.

Parsing Event from HTTP Request:
```go
// req is *http.Request
event, err := cloudEvents.FromHTTPRequest(req)
if err != nil {
panic("Unable to parse event from http Request: " + err.String())
}
fmt.Printf("eventType: %s", event.EventType)
```

Creating a minimal CloudEvent in version 0.1:
```go
import "github.com/dispatchframework/cloudevents-go-sdk/v01"
event := v01.Event{
EventType: "com.example.file.created",
Source: "/providers/Example.COM/storage/account#fileServices/default/{new-file}",
EventID: "ea35b24ede421",
}
```

The goal of this package is to provide support for all released versions of CloudEvents, ideally while maintaining
the same API. It will use semantic versioning with following rules:
* MAJOR version increments when backwards incompatible changes is introduced.
* MINOR version increments when backwards compatible feature is introduced INCLUDING support for new CloudEvents version.
* PATCH version increments when a backwards compatible bug fix is introduced.


## TODO list

- [ ] Add encoders registry, where SDK user can register their custom content-type encoders/decoders
- [ ] Add more tests for edge cases

## Existing Go for CloudEvents

Existing projects that added support for CloudEvents in Go are listed below. It's our goal to identify existing patterns
of using CloudEvents in Go-based project and design the SDK to support these patterns (where it makes sense).
- https://github.com/google/cloudevents-demo/tree/master/pkg/event
- https://github.com/vmware/dispatch/blob/master/pkg/events/cloudevent.go
- https://github.com/serverless/event-gateway/tree/master/event
28 changes: 28 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Package cloudevents provides primitives to work with CloudEvents specification: https://github.com/cloudevents/spec.
Parsing Event from HTTP Request:
// req is *http.Request
event, err := cloudEvents.FromHTTPRequest(req)
if err != nil {
panic("Unable to parse event from http Request: " + err.String())
}
Creating a minimal CloudEvent in version 0.1:
import "github.com/dispatchframework/cloudevents-go-sdk/v01"
event := v01.Event{
EventType: "com.example.file.created",
Source: "/providers/Example.COM/storage/account#fileServices/default/{new-file}",
EventID: "ea35b24ede421",
}
The goal of this package is to provide support for all released versions of CloudEvents, ideally while maintaining
the same API. It will use semantic versioning with following rules:
* MAJOR version increments when backwards incompatible changes is introduced.
* MINOR version increments when backwards compatible feature is introduced INCLUDING support for new CloudEvents version.
* PATCH version increments when a backwards compatible bug fix is introduced.
*/
package cloudevents
30 changes: 30 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cloudevents

// RequiredPropertyError is return when a property of an event that is required by specification is not set
type RequiredPropertyError string

func (e RequiredPropertyError) Error() string {
return "missing required property " + string(e)
}

// VersionMismatchError is returned when expected CloudEvent version does not match the actual one, e.g.
// when using GetEventV01 or when using transport bindings.
type VersionMismatchError string

func (e VersionMismatchError) Error() string {
return "provided event is not CloudEvent or does not implement expected version: " + string(e)
}

// VersionNotSupportedError is returned when provided version is not supported by this library.
type VersionNotSupportedError string

func (e VersionNotSupportedError) Error() string {
return "provided version " + string(e) + " is not supported"
}

// ContentTypeNotSupportedError is returned when povided event's content type is not supported by this library.
type ContentTypeNotSupportedError string

func (e ContentTypeNotSupportedError) Error() string {
return "provided content type " + string(e) + " is not supported"
}
18 changes: 18 additions & 0 deletions event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cloudevents

// Version01 holds a version string for CloudEvents specification version 0.1. See also EventV01 interface
// https://github.com/cloudevents/spec/blob/v0.1/spec.md
const Version01 = "0.1"

// Event interface is a generic abstraction over all possible versions and implementations of CloudEvents.
type Event interface {
// CloudEventVersion returns the version of Event specification followed by the underlying implementation.
CloudEventVersion() string
// Get takes a property name and, if it exists, returns the value of that property. The ok return value can
// be used to verify if the property exists.
Get(property string) (value interface{}, ok bool)
// Set sets the property value
Set(property string, value interface{})
// Properties returns a map of all event properties as keys and their mandatory status as values
Properties() map[string]bool
}
16 changes: 16 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cloudevents

import (
"net/http"

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

// 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

}
8 changes: 8 additions & 0 deletions httptransport/webhooks/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package webhooks

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

// Deliver delivers the event to the endpoint
func Deliver(event cloudevents.Event) (string, error) {

}
2 changes: 2 additions & 0 deletions httptransport/webhooks/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package webhooks implements the HTTP webhooks spec as defined in https://github.com/cloudevents/spec/blob/86e443ca8575374e281048779a39c6734d3cd58e/http-webhook.md.
package webhooks
12 changes: 12 additions & 0 deletions httptransport/webhooks/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package webhooks

import "net/http"

// WebHook is an HTTP middleware handling webhook requests according to HTTP Webhook spec
type WebHook struct {
}

// ServeHTTP implements http.Handler interface
func (w *WebHook) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
panic("not implemented")
}
131 changes: 131 additions & 0 deletions v01/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package v01

import (
"encoding/json"
"reflect"
"strings"
"time"

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

const (
eventTypeKey = "eventType"
eventTypeVersionKey = "eventTypeVersion"
sourceKey = "sourceKey"
eventIDKey = "eventID"
eventTimeKey = "eventTime"
schemaURLKey = "schemaURL"
contentTypeKey = "contentType"
extensionsKey = "extensions"
dataKey = "data"
)

// Event implements the the CloudEvents specification version 0.1
// https://github.com/cloudevents/spec/blob/v0.1/spec.md
type Event struct {
// EventType is a mandatory property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#eventtype
EventType string `json:"eventType"`
// EventTypeVersion is an optional property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#eventtypeversion
EventTypeVersion string `json:"eventTypeVersion,omitempty"`
// Source is a mandatory property
// TODO: ensure URI parsing
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#source
Source string `json:"source"`
// EventID is a mandatory property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#eventid
EventID string `json:"eventID"`
// EventTime is an optional property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#eventtime
EventTime *time.Time `json:"eventTime,omitempty"`
// SchemaURL is an optional property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#schemaurl
SchemaURL string `json:"schemaURL,omitempty"`
// ContentType is an optional property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#contenttype
ContentType string `json:"contentType,omitempty"`
// Extensions is an optional property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#extensions
Extensions map[string]interface{} `json:"extensions,omitempty"`
// Data is an optional property
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#data-1
Data interface{} `json:"data,omitempty"`
}

// CloudEventVersion returns the CloudEvents specification version supported by this implementation
func (Event) CloudEventVersion() (version string) {
return cloudevents.Version01
}

// Properties returns the map of all supported properties in version 0.1.
// The map value says whether particular property is required.
func (Event) Properties() map[string]bool {
return map[string]bool{
eventTypeKey: true,
sourceKey: true,
eventIDKey: true,
eventTypeVersionKey: false,
eventTimeKey: false,
schemaURLKey: false,
contentTypeKey: false,
extensionsKey: false,
dataKey: false,
}
}

// Get implements a generic getter method
func (e *Event) Get(property string) (interface{}, bool) {
field := reflect.ValueOf(e).Elem().FieldByName(strings.Title(property))
if field.IsValid() {
return field.Interface(), true
}
if e.Extensions == nil {
return nil, false
}
if value, ok := e.Extensions[property]; ok {
return value, ok
}
return nil, false
}

// Set sets the arbitrary property of event.
func (e *Event) Set(property string, value interface{}) {
field := reflect.ValueOf(e).Elem().FieldByName(strings.Title(property))
if field.IsValid() {
field.Set(reflect.ValueOf(value))
return
}

if e.Extensions == nil {
e.Extensions = make(map[string]interface{})
}
e.Extensions[property] = value
}

// Validate returns an error if the event is not correct according to the spec.
func (e *Event) Validate() error {
if e.EventType == "" {
return cloudevents.RequiredPropertyError(eventTypeKey)
}
if e.EventID == "" {
return cloudevents.RequiredPropertyError(eventIDKey)
}
if e.Source == "" {
return cloudevents.RequiredPropertyError(sourceKey)
}
}

// MarshalJSON implements the JSON Marshaler interface.
func (e *Event) MarshalJSON() ([]byte, error) {
type tmp Event
eventWithVersion := struct {
CloudEventVersion string `json:"cloudEventVersion"`
*tmp
}{
CloudEventVersion: cloudevents.Version01,
tmp: (*tmp)(e),
}
return json.Marshal(&eventWithVersion)
}
79 changes: 79 additions & 0 deletions v01/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package v01_test

import (
"encoding/json"
"reflect"
"testing"

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

func TestNewEvent(t *testing.T) {
event := &v01.Event{
EventType: "testType",
EventTypeVersion: "testVersion",
Source: "version",
EventID: "12345",
EventTime: nil,
SchemaURL: "http://example.com/schema",
ContentType: "application/json",
Extensions: nil,
Data: map[string]interface{}{"key": "value"},
}
data, err := json.Marshal(event)
if err != nil {
t.Errorf("JSON Error received: %v", err)
}

eventUnmarshaled := &v01.Event{}
json.Unmarshal(data, eventUnmarshaled)
if !reflect.DeepEqual(event, eventUnmarshaled) {
t.Errorf("source event %#v and unmarshaled event %#v are not equal", event, eventUnmarshaled)
}
}

func TestGetSet(t *testing.T) {
event := &v01.Event{
EventType: "testType",
EventTypeVersion: "testVersion",
Source: "version",
EventID: "12345",
EventTime: nil,
SchemaURL: "http://example.com/schema",
ContentType: "application/json",
Extensions: nil,
Data: map[string]interface{}{"key": "value"},
}

value, ok := event.Get("nonexistent")
if ok {
t.Error("Get ok for nonexistent key shoud be false, but isn't")
}
if value != nil {
t.Error("Get value for nonexistent key should be nil, but isn't")
}

value, ok = event.Get("contentType")
if !ok {
t.Error("Get ok for existing key shoud be true, but isn't")

}
if value != "application/json" {
t.Errorf("Get value for contentType should be application/json, but is %s", value)
}

event.Set("eventType", "newType")
if event.EventType != "newType" {
t.Errorf("expected eventType to be newType, got %s", event.EventType)
}

event.Set("ext", "somevalue")
value, ok = event.Get("ext")
if !ok {
t.Error("Get ok for ext key shoud be false, but isn't")
}
if value != "somevalue" {
t.Errorf("Get value for ext key should be somevalue, but is %s", value)
}

}
Loading

0 comments on commit 783d5b3

Please sign in to comment.