diff --git a/docs/Hook-Examples.md b/docs/Hook-Examples.md index 23896a7..cb4145e 100644 --- a/docs/Hook-Examples.md +++ b/docs/Hook-Examples.md @@ -425,6 +425,57 @@ Travis sends webhooks as `payload=`, so the payload needs to be par ] ``` +## JSON Array Payload + +If the JSON payload is an array instead of an object, `webhook` will process the payload and place it into a "root" object. +Therefore, references to payload values must begin with `root.`. + +For example, given the following payload (taken from the Sendgrid Event Webhook documentation): +```json +[ + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "processed", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "deferred", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "response": "400 try again later", + "attempt": "5" + } +] +``` + +A reference to the second item in the array would look like this: +```json +[ + { + "id": "sendgrid", + "execute-command": "{{ .Hookecho }}", + "trigger-rule": { + "match": { + "type": "value", + "parameter": { + "source": "payload", + "name": "root.1.event" + }, + "value": "deferred" + } + } + } +] +``` + ## XML Payload Given the following payload: diff --git a/test/hooks.json.tmpl b/test/hooks.json.tmpl index 71673ce..7fe7c8c 100644 --- a/test/hooks.json.tmpl +++ b/test/hooks.json.tmpl @@ -168,6 +168,22 @@ ], } }, + { + "id": "sendgrid", + "execute-command": "{{ .Hookecho }}", + "command-working-directory": "/", + "response-message": "success", + "trigger-rule": { + "match": { + "type": "value", + "parameter": { + "source": "payload", + "name": "root.0.event" + }, + "value": "processed" + } + } + }, { "id": "plex", "execute-command": "{{ .Hookecho }}", diff --git a/test/hooks.yaml.tmpl b/test/hooks.yaml.tmpl index 7c6e201..ace4277 100644 --- a/test/hooks.yaml.tmpl +++ b/test/hooks.yaml.tmpl @@ -97,6 +97,18 @@ name: "app.messages.message.#text" value: "Hello!!" +- id: sendgrid + execute-command: '{{ .Hookecho }}' + command-working-directory: / + response-message: success + trigger-rule: + match: + type: value + parameter: + source: payload + name: root.0.event + value: processed + - id: plex trigger-rule: match: diff --git a/webhook.go b/webhook.go index 1af2ce5..6a258c4 100644 --- a/webhook.go +++ b/webhook.go @@ -16,6 +16,7 @@ import ( "path/filepath" "strings" "time" + "unicode" "github.com/adnanh/webhook/internal/hook" "github.com/adnanh/webhook/internal/middleware" @@ -141,7 +142,7 @@ func main() { } if *logPath != "" { - file, err := os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + file, err := os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666) if err != nil { logQueue = append(logQueue, fmt.Sprintf("error opening log file %q: %v", *logPath, err)) // we'll bail out below @@ -385,9 +386,29 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(bytes.NewReader(body)) decoder.UseNumber() - err := decoder.Decode(&payload) - if err != nil { - log.Printf("[%s] error parsing JSON payload %+v\n", rid, err) + var firstChar byte + for i := 0; i < len(body); i++ { + if unicode.IsSpace(rune(body[i])) { + continue + } + firstChar = body[i] + break + } + + if firstChar == byte('[') { + var arrayPayload interface{} + err := decoder.Decode(&arrayPayload) + if err != nil { + log.Printf("[%s] error parsing JSON array payload %+v\n", rid, err) + } + + payload = make(map[string]interface{}, 1) + payload["root"] = arrayPayload + } else { + err := decoder.Decode(&payload) + if err != nil { + log.Printf("[%s] error parsing JSON payload %+v\n", rid, err) + } } case strings.Contains(contentType, "x-www-form-urlencoded"): @@ -648,7 +669,7 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in return string(out), err } -func writeHttpResponseCode(w http.ResponseWriter, rid string, hookId string, responseCode int) { +func writeHttpResponseCode(w http.ResponseWriter, rid, hookId string, responseCode int) { // Check if the given return code is supported by the http package // by testing if there is a StatusText for this code. if len(http.StatusText(responseCode)) > 0 { diff --git a/webhook_test.go b/webhook_test.go index 6dda780..4671c24 100644 --- a/webhook_test.go +++ b/webhook_test.go @@ -547,6 +547,28 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00 `success`, ``, }, + { + "payload-json-array", + "sendgrid", + nil, + "POST", + nil, + "application/json", + `[ + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "processed", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id" + } +]`, + http.StatusOK, + `success`, + ``, + }, { "multipart", "plex",