diff --git a/api/agent/protocol/cloudevent.go b/api/agent/protocol/cloudevent.go index 203a06484..d9f9a4eaa 100644 --- a/api/agent/protocol/cloudevent.go +++ b/api/agent/protocol/cloudevent.go @@ -21,11 +21,11 @@ type CloudEvent struct { Source string `json:"source"` EventType string `json:"eventType"` EventTypeVersion string `json:"eventTypeVersion"` - EventTime time.Time `json:"eventTime"` // TODO: ensure rfc3339 format + EventTime time.Time `json:"eventTime"` SchemaURL string `json:"schemaURL"` ContentType string `json:"contentType"` Extensions map[string]interface{} `json:"extensions"` - Data interface{} `json:"data"` // from docs: the payload is encoded into a media format which is specified by the contentType attribute (e.g. application/json) + Data interface{} `json:"data,omitempty"` // docs: the payload is encoded into a media format which is specified by the contentType attribute (e.g. application/json) } type cloudEventIn struct { @@ -63,6 +63,8 @@ func (h *CloudEventProtocol) writeJSONToContainer(ci CallInfo) error { return err } + // TODO: handle binary + var in cloudEventIn if ci.IsCloudEvent() { // then it's already in the right format, let's parse it, then modify @@ -80,18 +82,13 @@ func (h *CloudEventProtocol) writeJSONToContainer(ci CallInfo) error { Source: ci.RequestURL(), }, } - if buf.Len() == 0 { - // nada - // todo: should we leave as null, pass in empty string, omitempty or some default for the content type, eg: {} for json? - } else if ci.ContentType() == "application/json" { - d := map[string]interface{}{} - err = json.NewDecoder(buf).Decode(&d) + // NOTE: data is an optional field, we can leave it as nil + if buf.Len() > 0 { + // NOTE: if it's not contentType=application/json, then a string is a valid json value, so this will work. + err := json.NewDecoder(buf).Decode(&in.Data) if err != nil { return fmt.Errorf("Invalid json body with contentType 'application/json'. %v", err) } - in.Data = d - } else { - in.Data = buf.String() } } // todo: deal with the dual ID's, one from outside, one from inside diff --git a/api/agent/protocol/cloudevent_test.go b/api/agent/protocol/cloudevent_test.go new file mode 100644 index 000000000..be8bed402 --- /dev/null +++ b/api/agent/protocol/cloudevent_test.go @@ -0,0 +1,100 @@ +package protocol + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/go-openapi/strfmt" +) + +// implements CallInfo, modify as needed +type testCall struct { + cloud bool + contentType string + input io.Reader +} + +func (t *testCall) IsCloudEvent() bool { return t.cloud } +func (t *testCall) CallID() string { return "foo" } +func (t *testCall) ContentType() string { return t.contentType } +func (t *testCall) Input() io.Reader { return t.input } +func (t *testCall) Deadline() strfmt.DateTime { + return strfmt.DateTime(time.Now().Add(30 * time.Second)) +} +func (t *testCall) CallType() string { return "sync" } +func (t *testCall) ProtocolType() string { return "http" } +func (t *testCall) Request() *http.Request { return nil } // unused here +func (t *testCall) Method() string { return "GET" } +func (t *testCall) RequestURL() string { return "http://example.com/r/yo/dawg" } +func (t *testCall) Headers() map[string][]string { return map[string][]string{} } + +func TestJSONMap(t *testing.T) { + in := strings.NewReader(`{"yo":"dawg"}`) + + var ib, ob bytes.Buffer + cep := &CloudEventProtocol{ + in: &ib, + out: &ob, + } + + tc := &testCall{false, "application/json; charset=utf-8", in} + + err := cep.writeJSONToContainer(tc) + if err != nil { + t.Fatal(err) + } + + var oce CloudEvent + err = json.NewDecoder(&ib).Decode(&oce) + if err != nil { + t.Fatal(err) + } + + mappo, ok := oce.Data.(map[string]interface{}) + if !ok { + t.Fatalf("data field should be map[string]interface{}: %T", oce.Data) + } + + v, ok := mappo["yo"].(string) + if v != "dawg" { + t.Fatal("value in map is wrong", v) + } +} + +func TestJSONNotMap(t *testing.T) { + // we accept all json values here https://tools.ietf.org/html/rfc7159#section-3 + in := strings.NewReader(`true`) + + var ib, ob bytes.Buffer + cep := &CloudEventProtocol{ + in: &ib, + out: &ob, + } + + tc := &testCall{false, "application/json", in} + + err := cep.writeJSONToContainer(tc) + if err != nil { + t.Fatal(err) + } + + var oce CloudEvent + err = json.NewDecoder(&ib).Decode(&oce) + if err != nil { + t.Fatal(err) + } + + boolo, ok := oce.Data.(bool) + if !ok { + t.Fatalf("data field should be bool: %T", oce.Data) + } + + if !boolo { + t.Fatal("bool should be true", boolo) + } +}