forked from rjeczalik/gh
/
webhook.go
175 lines (166 loc) · 6.19 KB
/
webhook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Package webhook implements middleware for GitHub Webhooks. User provides
// webhook service object that handles events delivered by GitHub. Webhook
// handler verifies payload signature delivered along with the event, unmarshals
// it to corresponding event struct and dispatches control to user service.
//
// The types of events are configured up front during webhook creation. Only
// "application/json" content type is supported for incoming events.
//
// Event types
//
// Name | Type
// -------------------+-----------------------------
// commit_comment | *webhook.CommitCommentEvent
// -------------------+-----------------------------
// create | *webhook.CreateEvent
// -------------------+-----------------------------
// delete | *webhook.DeleteEvent
// -------------------+-----------------------------
// deployment | *webhook.DeploymentEvent
// -------------------+-----------------------------
// deployment_status | *webhook.DeploymentStatusEvent
// -------------------+-----------------------------
// download | *webhook.DownloadEvent
// -------------------+-----------------------------
// follow | *webhook.FollowEvent
// -------------------+-----------------------------
// fork_apply | *webhook.ForkApplyEvent
// -------------------+-----------------------------
// fork | *webhook.ForkEvent
// -------------------+-----------------------------
// gist | *webhook.GistEvent
// -------------------+-----------------------------
// gollum | *webhook.GollumEvent
// -------------------+-----------------------------
// issue_comment | *webhook.IssueCommentEvent
// -------------------+-----------------------------
// issues | *webhook.IssuesEvent
// -------------------+-----------------------------
// member | *webhook.MemberEvent
// -------------------+-----------------------------
// membership | *webhook.MembershipEvent
// -------------------+-----------------------------
// page_build | *webhook.PageBuildEvent
// -------------------+-----------------------------
// ping | *webhook.PingEvent
// -------------------+-----------------------------
// public | *webhook.PublicEvent
// -------------------+-----------------------------
// pull_request | *webhook.PullRequestEvent
// -------------------+-----------------------------
// push | *webhook.PushEvent
// -------------------+-----------------------------
// release | *webhook.ReleaseEvent
// -------------------+-----------------------------
// repository | *webhook.RepositoryEvent
// -------------------+-----------------------------
// status | *webhook.StatusEvent
// -------------------+-----------------------------
// team_add | *webhook.TeamAddEvent
// -------------------+-----------------------------
// watch | *webhook.WatchEvent
// -------------------+---------+----------------------------------------
// pull_request_review_comment | *webhook.PullRequestReviewCommentEvent
// -----------------------------+----------------------------------------
//
// Handler service
//
// Webhook dispatches incoming events to user-provided handler service. Each
// method that takes *Event struct as a single argument is mapped for handling
// corresponding event type according to the above table. In order to handle
// all the events with single method, webhook handler looks up for the method
// with the following definition:
//
// func (T) MethodName(eventName string, eventPayload interface{})
//
// If a handler service has defined both: methods for handling particular events
// and method hadling all events, the former has the priority - if there exists
// no method for handling particular event type, the blanket handler will be used.
//
// Example
//
// The following handler service logs each incoming event.
//
// package main
//
// import (
// "log"
// "net/http"
//
// "github.com/rjeczalik/gh/webhook"
// )
//
// type LoggerService struct{}
//
// func (LoggerService) Ping(event *webhook.PingEvent) {
// log.Printf("supported events: %v", event.Hook.Events)
// }
//
// func (LoggerService) Push(event *webhook.PushEvent) {
// log.Printf("%s has pushed to %s", event.Pusher.Email, event.Repository.Name)
// }
//
// func (LoggerService) All(name string, event interface{}) {
// log.Println("event", event)
// }
//
// func main() {
// log.Fatal(http.ListenAndServe(":8080", webhook.New("secret", LoggerService{}))
// }
//
// The "ping" and "push" event are handle accordingly by the Ping and Push methods,
// all the rest are handled with the All one.
package webhook
import (
"bytes"
"reflect"
"strconv"
"time"
)
//go:generate go run generate_payloads.go
//go:generate go test -run TestGenerateMockHelper -- -generate
//go:generate gofmt -w -s payloads.go mock_test.go
var null = []byte("null")
// Time embeds time.Time. The wrapper allows for unmarshalling time from JSON
// null value or unix timestamp.
type Time struct {
time.Time
}
// MarshalJSON implements the json.Marshaler interface. The time is a quoted
// string in RFC 3339 format or "null" if it's a zero value.
func (t Time) MarshalJSON() ([]byte, error) {
if t.Time.IsZero() {
return null, nil
}
return t.Time.MarshalJSON()
}
// UnmarshalJSON implements the json.Unmarshaler interface. The time is expected
// to be a quoted string in RFC 3339 format, a unix timestamp or a "null" string.
func (t *Time) UnmarshalJSON(p []byte) (err error) {
if bytes.Compare(p, null) == 0 {
t.Time = time.Time{}
return nil
}
if err = t.Time.UnmarshalJSON(p); err == nil {
return nil
}
n, e := strconv.ParseInt(string(bytes.Trim(p, `"`)), 10, 64)
if e != nil {
return err
}
t.Time = time.Unix(n, 0)
return nil
}
type payloadsMap map[string]reflect.Type
func (p payloadsMap) Type(name string) (reflect.Type, bool) {
typ, ok := p[name]
return typ, ok
}
func (p payloadsMap) Name(typ reflect.Type) (string, bool) {
for pname, ptyp := range p {
if ptyp == typ {
return pname, true
}
}
return "", false
}