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

Add WebhookTypes and EventForType methods #2865

Merged
merged 9 commits into from
Aug 16, 2023
131 changes: 5 additions & 126 deletions github/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,132 +27,11 @@ func (e Event) String() string {

// ParsePayload parses the event payload. For recognized event types,
// a value of the corresponding struct type will be returned.
func (e *Event) ParsePayload() (payload interface{}, err error) {
switch *e.Type {
case "BranchProtectionRuleEvent":
payload = &BranchProtectionRuleEvent{}
case "CheckRunEvent":
payload = &CheckRunEvent{}
case "CheckSuiteEvent":
payload = &CheckSuiteEvent{}
case "CodeScanningAlertEvent":
payload = &CodeScanningAlertEvent{}
case "CommitCommentEvent":
payload = &CommitCommentEvent{}
case "ContentReferenceEvent":
payload = &ContentReferenceEvent{}
case "CreateEvent":
payload = &CreateEvent{}
case "DeleteEvent":
payload = &DeleteEvent{}
case "DeployKeyEvent":
payload = &DeployKeyEvent{}
case "DeploymentEvent":
payload = &DeploymentEvent{}
case "DeploymentProtectionRuleEvent":
payload = &DeploymentProtectionRuleEvent{}
case "DeploymentStatusEvent":
payload = &DeploymentStatusEvent{}
case "DiscussionEvent":
payload = &DiscussionEvent{}
case "DiscussionCommentEvent":
payload = &DiscussionCommentEvent{}
case "ForkEvent":
payload = &ForkEvent{}
case "GitHubAppAuthorizationEvent":
payload = &GitHubAppAuthorizationEvent{}
case "GollumEvent":
payload = &GollumEvent{}
case "InstallationEvent":
payload = &InstallationEvent{}
case "InstallationRepositoriesEvent":
payload = &InstallationRepositoriesEvent{}
case "InstallationTargetEvent":
payload = &InstallationTargetEvent{}
case "IssueCommentEvent":
payload = &IssueCommentEvent{}
case "IssuesEvent":
payload = &IssuesEvent{}
case "LabelEvent":
payload = &LabelEvent{}
case "MarketplacePurchaseEvent":
payload = &MarketplacePurchaseEvent{}
case "MemberEvent":
payload = &MemberEvent{}
case "MembershipEvent":
payload = &MembershipEvent{}
case "MergeGroupEvent":
payload = &MergeGroupEvent{}
case "MetaEvent":
payload = &MetaEvent{}
case "MilestoneEvent":
payload = &MilestoneEvent{}
case "OrganizationEvent":
payload = &OrganizationEvent{}
case "OrgBlockEvent":
payload = &OrgBlockEvent{}
case "PackageEvent":
payload = &PackageEvent{}
case "PageBuildEvent":
payload = &PageBuildEvent{}
case "PersonalAccessTokenRequestEvent":
payload = &PersonalAccessTokenRequestEvent{}
case "PingEvent":
payload = &PingEvent{}
case "ProjectEvent":
payload = &ProjectEvent{}
case "ProjectCardEvent":
payload = &ProjectCardEvent{}
case "ProjectColumnEvent":
payload = &ProjectColumnEvent{}
case "PublicEvent":
payload = &PublicEvent{}
case "PullRequestEvent":
payload = &PullRequestEvent{}
case "PullRequestReviewEvent":
payload = &PullRequestReviewEvent{}
case "PullRequestReviewCommentEvent":
payload = &PullRequestReviewCommentEvent{}
case "PullRequestReviewThreadEvent":
payload = &PullRequestReviewThreadEvent{}
case "PullRequestTargetEvent":
payload = &PullRequestTargetEvent{}
case "PushEvent":
payload = &PushEvent{}
case "ReleaseEvent":
payload = &ReleaseEvent{}
case "RepositoryEvent":
payload = &RepositoryEvent{}
case "RepositoryDispatchEvent":
payload = &RepositoryDispatchEvent{}
case "RepositoryImportEvent":
payload = &RepositoryImportEvent{}
case "RepositoryVulnerabilityAlertEvent":
payload = &RepositoryVulnerabilityAlertEvent{}
case "SecretScanningAlertEvent":
payload = &SecretScanningAlertEvent{}
case "SecurityAdvisoryEvent":
payload = &SecurityAdvisoryEvent{}
case "StarEvent":
payload = &StarEvent{}
case "StatusEvent":
payload = &StatusEvent{}
case "TeamEvent":
payload = &TeamEvent{}
case "TeamAddEvent":
payload = &TeamAddEvent{}
case "UserEvent":
payload = &UserEvent{}
case "WatchEvent":
payload = &WatchEvent{}
case "WorkflowDispatchEvent":
payload = &WorkflowDispatchEvent{}
case "WorkflowJobEvent":
payload = &WorkflowJobEvent{}
case "WorkflowRunEvent":
payload = &WorkflowRunEvent{}
}
err = json.Unmarshal(*e.RawPayload, &payload)
func (e *Event) ParsePayload() (interface{}, error) {
// It would be nice if e.Type were the snake_case name of the event,
// but the existing interface uses the struct name instead.
payload := EventForType(typeToMessageMapping[e.GetType()])
err := json.Unmarshal(*e.RawPayload, &payload)
evankanderson marked this conversation as resolved.
Show resolved Hide resolved
return payload, err
}

Expand Down
161 changes: 98 additions & 63 deletions github/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"mime"
"net/http"
"net/url"
"reflect"
"strings"
)

Expand All @@ -43,71 +44,83 @@ const (

var (
// eventTypeMapping maps webhooks types to their corresponding go-github struct types.
eventTypeMapping = map[string]string{
"branch_protection_rule": "BranchProtectionRuleEvent",
"check_run": "CheckRunEvent",
"check_suite": "CheckSuiteEvent",
"code_scanning_alert": "CodeScanningAlertEvent",
"commit_comment": "CommitCommentEvent",
"content_reference": "ContentReferenceEvent",
"create": "CreateEvent",
"delete": "DeleteEvent",
"deploy_key": "DeployKeyEvent",
"deployment": "DeploymentEvent",
"deployment_status": "DeploymentStatusEvent",
"deployment_protection_rule": "DeploymentProtectionRuleEvent",
"discussion": "DiscussionEvent",
"discussion_comment": "DiscussionCommentEvent",
"fork": "ForkEvent",
"github_app_authorization": "GitHubAppAuthorizationEvent",
"gollum": "GollumEvent",
"installation": "InstallationEvent",
"installation_repositories": "InstallationRepositoriesEvent",
"installation_target": "InstallationTargetEvent",
"issue_comment": "IssueCommentEvent",
"issues": "IssuesEvent",
"label": "LabelEvent",
"marketplace_purchase": "MarketplacePurchaseEvent",
"member": "MemberEvent",
"membership": "MembershipEvent",
"merge_group": "MergeGroupEvent",
"meta": "MetaEvent",
"milestone": "MilestoneEvent",
"organization": "OrganizationEvent",
"org_block": "OrgBlockEvent",
"package": "PackageEvent",
"page_build": "PageBuildEvent",
"personal_access_token_request": "PersonalAccessTokenRequestEvent",
"ping": "PingEvent",
"project": "ProjectEvent",
"project_card": "ProjectCardEvent",
"project_column": "ProjectColumnEvent",
"public": "PublicEvent",
"pull_request": "PullRequestEvent",
"pull_request_review": "PullRequestReviewEvent",
"pull_request_review_comment": "PullRequestReviewCommentEvent",
"pull_request_review_thread": "PullRequestReviewThreadEvent",
"pull_request_target": "PullRequestTargetEvent",
"push": "PushEvent",
"repository": "RepositoryEvent",
"repository_dispatch": "RepositoryDispatchEvent",
"repository_import": "RepositoryImportEvent",
"repository_vulnerability_alert": "RepositoryVulnerabilityAlertEvent",
"release": "ReleaseEvent",
"secret_scanning_alert": "SecretScanningAlertEvent",
"security_advisory": "SecurityAdvisoryEvent",
"star": "StarEvent",
"status": "StatusEvent",
"team": "TeamEvent",
"team_add": "TeamAddEvent",
"user": "UserEvent",
"watch": "WatchEvent",
"workflow_dispatch": "WorkflowDispatchEvent",
"workflow_job": "WorkflowJobEvent",
"workflow_run": "WorkflowRunEvent",
eventTypeMapping = map[string]interface{}{
"branch_protection_rule": &BranchProtectionRuleEvent{},
gmlewis marked this conversation as resolved.
Show resolved Hide resolved
"check_run": &CheckRunEvent{},
"check_suite": &CheckSuiteEvent{},
"code_scanning_alert": &CodeScanningAlertEvent{},
"commit_comment": &CommitCommentEvent{},
"content_reference": &ContentReferenceEvent{},
"create": &CreateEvent{},
"delete": &DeleteEvent{},
"deploy_key": &DeployKeyEvent{},
"deployment": &DeploymentEvent{},
"deployment_status": &DeploymentStatusEvent{},
"deployment_protection_rule": &DeploymentProtectionRuleEvent{},
"discussion": &DiscussionEvent{},
"discussion_comment": &DiscussionCommentEvent{},
"fork": &ForkEvent{},
"github_app_authorization": &GitHubAppAuthorizationEvent{},
"gollum": &GollumEvent{},
"installation": &InstallationEvent{},
"installation_repositories": &InstallationRepositoriesEvent{},
"installation_target": &InstallationTargetEvent{},
"issue_comment": &IssueCommentEvent{},
"issues": &IssuesEvent{},
"label": &LabelEvent{},
"marketplace_purchase": &MarketplacePurchaseEvent{},
"member": &MemberEvent{},
"membership": &MembershipEvent{},
"merge_group": &MergeGroupEvent{},
"meta": &MetaEvent{},
"milestone": &MilestoneEvent{},
"organization": &OrganizationEvent{},
"org_block": &OrgBlockEvent{},
"package": &PackageEvent{},
"page_build": &PageBuildEvent{},
"personal_access_token_request": &PersonalAccessTokenRequestEvent{},
"ping": &PingEvent{},
"project": &ProjectEvent{},
"project_card": &ProjectCardEvent{},
"project_column": &ProjectColumnEvent{},
"public": &PublicEvent{},
"pull_request": &PullRequestEvent{},
"pull_request_review": &PullRequestReviewEvent{},
"pull_request_review_comment": &PullRequestReviewCommentEvent{},
"pull_request_review_thread": &PullRequestReviewThreadEvent{},
"pull_request_target": &PullRequestTargetEvent{},
"push": &PushEvent{},
"repository": &RepositoryEvent{},
"repository_dispatch": &RepositoryDispatchEvent{},
"repository_import": &RepositoryImportEvent{},
"repository_vulnerability_alert": &RepositoryVulnerabilityAlertEvent{},
"release": &ReleaseEvent{},
"secret_scanning_alert": &SecretScanningAlertEvent{},
"security_advisory": &SecurityAdvisoryEvent{},
"star": &StarEvent{},
"status": &StatusEvent{},
"team": &TeamEvent{},
"team_add": &TeamAddEvent{},
"user": &UserEvent{},
"watch": &WatchEvent{},
"workflow_dispatch": &WorkflowDispatchEvent{},
"workflow_job": &WorkflowJobEvent{},
"workflow_run": &WorkflowRunEvent{},
}
// forward mapping of event types to the sting names of the structs
gmlewis marked this conversation as resolved.
Show resolved Hide resolved
messageToTypeName = make(map[string]string, len(eventTypeMapping))
// Inverse map of the above
typeToMessageMapping = make(map[string]string, len(eventTypeMapping))
)

func init() {
for k, v := range eventTypeMapping {
typename := reflect.TypeOf(v).Elem().Name()
messageToTypeName[k] = typename
typeToMessageMapping[typename] = k
}
}

// genMAC generates the HMAC signature for a message provided the secret key
// and hashFunc.
func genMAC(message, key []byte, hashFunc func() hash.Hash) []byte {
Expand Down Expand Up @@ -296,7 +309,7 @@ func DeliveryID(r *http.Request) string {
// }
// }
func ParseWebHook(messageType string, payload []byte) (interface{}, error) {
eventType, ok := eventTypeMapping[messageType]
eventType, ok := messageToTypeName[messageType]
if !ok {
return nil, fmt.Errorf("unknown X-Github-Event in message: %v", messageType)
}
Expand All @@ -307,3 +320,25 @@ func ParseWebHook(messageType string, payload []byte) (interface{}, error) {
}
return event.ParsePayload()
}

// WebhookTypes returns a list of all the known GitHub event type strings
gmlewis marked this conversation as resolved.
Show resolved Hide resolved
// supported by go-github.
func MessageTypes() []string {
types := make([]string, 0, len(eventTypeMapping))
for t := range eventTypeMapping {
types = append(types, t)
}
return types
evankanderson marked this conversation as resolved.
Show resolved Hide resolved
}

// EventForType returns an empty struct matching the specified GitHub event type.
// If messageType does not match any known event types, it returns nil.
func EventForType(messageType string) interface{} {
prototype := eventTypeMapping[messageType]
if prototype != nil {
// return a _copy_ of the pointed-to-object. Unfortunately, for this
// we need to use reflection.
return reflect.New(reflect.TypeOf(prototype).Elem()).Interface()
}
return prototype
gmlewis marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 8 additions & 0 deletions github/messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,14 @@ func TestParseWebHook(t *testing.T) {
}
}

func TestAllMessageTypesMapped(t *testing.T) {
for _, mt := range MessageTypes() {
if obj := EventForType(mt); obj == nil {
t.Errorf("messageMap missing message type %q", mt)
}
}
}

func TestParseWebHook_BadMessageType(t *testing.T) {
if _, err := ParseWebHook("bogus message type", []byte("{}")); err == nil {
t.Fatal("ParseWebHook returned nil; wanted error")
Expand Down
4 changes: 2 additions & 2 deletions github/repos_hooks_deliveries.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ func (s *RepositoriesService) RedeliverHookDelivery(ctx context.Context, owner,
// ParseRequestPayload parses the request payload. For recognized event types,
// a value of the corresponding struct type will be returned.
func (d *HookDelivery) ParseRequestPayload() (interface{}, error) {
eType, ok := eventTypeMapping[*d.Event]
eType, ok := messageToTypeName[d.GetEvent()]
if !ok {
return nil, fmt.Errorf("unsupported event type %q", *d.Event)
return nil, fmt.Errorf("unsupported event type %q", d.GetEvent())
}

e := &Event{Type: &eType, RawPayload: d.Request.RawPayload}
Expand Down
Loading