From d028bba86fdd4ad8475d7f2ff7bea3e6699aed29 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 13 Dec 2022 03:35:57 +0100 Subject: [PATCH] [#61]: evPushSender can be null or missing (#60) --- examples/servant/src/Main.hs | 2 +- fixtures/push-event-without-sender.json | 148 ++++++++++++++++++++++++ github-webhooks.cabal | 1 + spec/DecodeEventsSpec.hs | 126 +++++++++++++++++++- src/GitHub/Data/Webhooks/Events.hs | 13 ++- 5 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 fixtures/push-event-without-sender.json diff --git a/examples/servant/src/Main.hs b/examples/servant/src/Main.hs index c05c02b..83112a7 100644 --- a/examples/servant/src/Main.hs +++ b/examples/servant/src/Main.hs @@ -35,7 +35,7 @@ type PushHookAPI pushHook :: RepoWebhookEvent -> ((), PushEvent) -> Handler () pushHook _ (_, ev) = liftIO $ do - putStrLn $ (show . whUserLogin . evPushSender) ev ++ " pushed a commit causing HEAD SHA to become:" + putStrLn $ (show . whUserLogin . fromJust . evPushSender) ev ++ " pushed a commit causing HEAD SHA to become:" print $ (fromJust . evPushHeadSha) ev -- Issue Comment Hook diff --git a/fixtures/push-event-without-sender.json b/fixtures/push-event-without-sender.json new file mode 100644 index 0000000..dfed3b8 --- /dev/null +++ b/fixtures/push-event-without-sender.json @@ -0,0 +1,148 @@ +{ + "ref": "refs/heads/gh-readonly-queue/main/pr-3072-287d30540ac5a1acc57d0bdc138fa81dee315f48", + "before": "a6bd77bbe77ee6ae8f9686621b192d70fcf83e2b", + "after": "0000000000000000000000000000000000000000", + "repository": { + "id": 353322834, + "node_id": "MDEwOJllG9zcXaRcvknzNYyTzM4OUQD=", + "name": "infra-core", + "full_name": "some-organization/infra-core", + "private": true, + "owner": { + "name": "some-organization", + "email": null, + "login": "some-organization", + "id": 52709322, + "node_id": "MDEyO9ky2ZuaXpFdhlGvjbzNzczMTO3Y", + "avatar_url": "https://avatars.githubusercontent.com/u/52709322?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/some-organization", + "html_url": "https://github.com/some-organization", + "followers_url": "https://api.github.com/users/some-organization/followers", + "following_url": "https://api.github.com/users/some-organization/following{/other_user}", + "gists_url": "https://api.github.com/users/some-organization/gists{/gist_id}", + "starred_url": "https://api.github.com/users/some-organization/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/some-organization/subscriptions", + "organizations_url": "https://api.github.com/users/some-organization/orgs", + "repos_url": "https://api.github.com/users/some-organization/repos", + "events_url": "https://api.github.com/users/some-organization/events{/privacy}", + "received_events_url": "https://api.github.com/users/some-organization/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/some-organization/infra-core", + "description": "Homo sapiens non urinat in ventum", + "fork": false, + "url": "https://github.com/some-organization/infra-core", + "forks_url": "https://api.github.com/repos/some-organization/infra-core/forks", + "keys_url": "https://api.github.com/repos/some-organization/infra-core/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/some-organization/infra-core/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/some-organization/infra-core/teams", + "hooks_url": "https://api.github.com/repos/some-organization/infra-core/hooks", + "issue_events_url": "https://api.github.com/repos/some-organization/infra-core/issues/events{/number}", + "events_url": "https://api.github.com/repos/some-organization/infra-core/events", + "assignees_url": "https://api.github.com/repos/some-organization/infra-core/assignees{/user}", + "branches_url": "https://api.github.com/repos/some-organization/infra-core/branches{/branch}", + "tags_url": "https://api.github.com/repos/some-organization/infra-core/tags", + "blobs_url": "https://api.github.com/repos/some-organization/infra-core/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/some-organization/infra-core/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/some-organization/infra-core/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/some-organization/infra-core/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/some-organization/infra-core/statuses/{sha}", + "languages_url": "https://api.github.com/repos/some-organization/infra-core/languages", + "stargazers_url": "https://api.github.com/repos/some-organization/infra-core/stargazers", + "contributors_url": "https://api.github.com/repos/some-organization/infra-core/contributors", + "subscribers_url": "https://api.github.com/repos/some-organization/infra-core/subscribers", + "subscription_url": "https://api.github.com/repos/some-organization/infra-core/subscription", + "commits_url": "https://api.github.com/repos/some-organization/infra-core/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/some-organization/infra-core/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/some-organization/infra-core/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/some-organization/infra-core/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/some-organization/infra-core/contents/{+path}", + "compare_url": "https://api.github.com/repos/some-organization/infra-core/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/some-organization/infra-core/merges", + "archive_url": "https://api.github.com/repos/some-organization/infra-core/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/some-organization/infra-core/downloads", + "issues_url": "https://api.github.com/repos/some-organization/infra-core/issues{/number}", + "pulls_url": "https://api.github.com/repos/some-organization/infra-core/pulls{/number}", + "milestones_url": "https://api.github.com/repos/some-organization/infra-core/milestones{/number}", + "notifications_url": "https://api.github.com/repos/some-organization/infra-core/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/some-organization/infra-core/labels{/name}", + "releases_url": "https://api.github.com/repos/some-organization/infra-core/releases{/id}", + "deployments_url": "https://api.github.com/repos/some-organization/infra-core/deployments", + "created_at": 1618165822, + "updated_at": "2022-11-07T17:04:28Z", + "pushed_at": 1670262117, + "git_url": "git://github.com/some-organization/infra-core.git", + "ssh_url": "git@github.com:some-organization/infra-core.git", + "clone_url": "https://github.com/some-organization/infra-core.git", + "svn_url": "https://github.com/some-organization/infra-core", + "homepage": "https://github.com/orgs/some-organization/projects/7", + "size": 23932, + "stargazers_count": 4, + "watchers_count": 4, + "language": "Brainfuck", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": true, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 130, + "license": { + "key": "other", + "name": "Other", + "spdx_id": "NOASSERTION", + "url": null, + "node_id": "MDc6GTjlW5ZZzAT=" + }, + "allow_forking": false, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "private", + "forks": 1, + "open_issues": 130, + "watchers": 4, + "default_branch": "main", + "stargazers": 4, + "master_branch": "main", + "organization": "some-organization" + }, + "pusher": { + "name": "none" + }, + "organization": { + "login": "some-organization", + "id": 52709322, + "node_id": "MDEyO9kZyFua2pXhGlvdjbzNcMzOTYz3", + "url": "https://api.github.com/orgs/some-organization", + "repos_url": "https://api.github.com/orgs/some-organization/repos", + "events_url": "https://api.github.com/orgs/some-organization/events", + "hooks_url": "https://api.github.com/orgs/some-organization/hooks", + "issues_url": "https://api.github.com/orgs/some-organization/issues", + "members_url": "https://api.github.com/orgs/some-organization/members{/member}", + "public_members_url": "https://api.github.com/orgs/some-organization/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/52709322?v=4", + "description": "Plurality Media" + }, + "installation": { + "id": 17816238, + "node_id": "MDIzklOdGunVcFm0WauSW9z5GFdsGb0FW9uaTMgM3zcNjg4=" + }, + "created": false, + "deleted": true, + "forced": false, + "base_ref": null, + "compare": "https://github.com/some-organization/infra-core/compare/a6bd77bbe77e...000000000000", + "commits": [ + + ], + "head_commit": null +} diff --git a/github-webhooks.cabal b/github-webhooks.cabal index 3f0c938..3fb539b 100644 --- a/github-webhooks.cabal +++ b/github-webhooks.cabal @@ -69,6 +69,7 @@ extra-source-files: fixtures/pull-request-event.json fixtures/pull-request-review-comment-event.json fixtures/pull-request-review-event.json + fixtures/push-event-without-sender.json fixtures/push-event.json fixtures/release-event.json fixtures/repository-event.json diff --git a/spec/DecodeEventsSpec.hs b/spec/DecodeEventsSpec.hs index 35fd2d3..f76a2c1 100644 --- a/spec/DecodeEventsSpec.hs +++ b/spec/DecodeEventsSpec.hs @@ -70,6 +70,7 @@ spec = do it "can decode PullRequestReviewCommentEvent" $ fixtureShouldMatch "fixtures/pull-request-review-comment-event.json" pullRequestReviewCommentEventFixture it "can decode PullRequestReviewEvent" $ fixtureShouldMatch "fixtures/pull-request-review-event.json" pullRequestReviewEventFixture it "can decode PushEvent" $ fixtureShouldMatch "fixtures/push-event.json" pushEventFixture + it "can decode PushEvent without sender" $ fixtureShouldMatch "fixtures/push-event-without-sender.json" pushEventFixtureWithoutSender it "can decode ReleaseEvent" $ fixtureShouldMatch "fixtures/release-event.json" releaseEventFixture it "can decode RepositoryEvent" $ fixtureShouldMatch "fixtures/repository-event.json" repositoryEventFixture it "can decode StatusEvent" $ fixtureShouldMatch "fixtures/status-event.json" statusEventFixture @@ -5631,7 +5632,7 @@ pushEventFixture = PushEvent } , evPushOrganization = Nothing , evPushSender = - HookUser + Just $ HookUser { whUserLogin = "baxterthehacker" , whUserId = 6752317 , whUserNodeId = "MDg6Q2hlY2tSdW4xMjg2MjAyMjg=" @@ -5653,6 +5654,129 @@ pushEventFixture = PushEvent } } +pushEventFixtureWithoutSender :: PushEvent +pushEventFixtureWithoutSender = + PushEvent + { evPushRef = "refs/heads/gh-readonly-queue/main/pr-3072-287d30540ac5a1acc57d0bdc138fa81dee315f48", + evPushHeadSha = Just "0000000000000000000000000000000000000000", + evPushBeforeSha = Just "a6bd77bbe77ee6ae8f9686621b192d70fcf83e2b", + evPushCreated = False, + evPushDeleted = True, + evPushForced = False, + evPushBaseRef = Nothing, + evPushCompareUrl = URL "https://github.com/some-organization/infra-core/compare/a6bd77bbe77e...000000000000", + evPushCommits = Just V.empty, + evPushHeadCommit = Nothing, + evPushRepository = + HookRepository + { whRepoId = 353322834, + whRepoNodeId = "MDEwOJllG9zcXaRcvknzNYyTzM4OUQD=", + whRepoName = "infra-core", + whRepoFullName = "some-organization/infra-core", + whRepoOwner = + Right + ( HookUser + { whUserLogin = "some-organization", + whUserId = 52709322, + whUserNodeId = "MDEyO9ky2ZuaXpFdhlGvjbzNzczMTO3Y", + whUserAvatarUrl = URL "https://avatars.githubusercontent.com/u/52709322?v=4", + whUserGravatarId = URL "", + whUserUrl = URL "https://api.github.com/users/some-organization", + whUserHtmlUrl = URL "https://github.com/some-organization", + whUserFollowersUrl = URL "https://api.github.com/users/some-organization/followers", + whUserFollowingUrl = URL "https://api.github.com/users/some-organization/following{/other_user}", + whUserGistsUrl = URL "https://api.github.com/users/some-organization/gists{/gist_id}", + whUserStarredUrl = URL "https://api.github.com/users/some-organization/starred{/owner}{/repo}", + whUserSubscriptionsUrl = URL "https://api.github.com/users/some-organization/subscriptions", + whUserOrganizationsUrl = URL "https://api.github.com/users/some-organization/orgs", + whUserReposUrl = URL "https://api.github.com/users/some-organization/repos", + whUserEventsUrl = URL "https://api.github.com/users/some-organization/events{/privacy}", + whUserReceivedEventsUrl = URL "https://api.github.com/users/some-organization/received_events", + whUserType = OwnerOrganization, + whUserIsAdminOfSite = False + } + ), + whRepoIsPrivate = True, + whRepoHtmlUrl = URL "https://github.com/some-organization/infra-core", + whRepoDescription = "Homo sapiens non urinat in ventum", + whRepoIsAFork = False, + whRepoUrl = URL "https://github.com/some-organization/infra-core", + whRepoForksUrl = URL "https://api.github.com/repos/some-organization/infra-core/forks", + whRepoKeysUrl = URL "https://api.github.com/repos/some-organization/infra-core/keys{/key_id}", + whRepoCollaboratorsUrl = URL "https://api.github.com/repos/some-organization/infra-core/collaborators{/collaborator}", + whRepoTeamsUrl = URL "https://api.github.com/repos/some-organization/infra-core/teams", + whRepoHooksUrl = URL "https://api.github.com/repos/some-organization/infra-core/hooks", + whRepoIssueEventsUrl = URL "https://api.github.com/repos/some-organization/infra-core/issues/events{/number}", + whRepoEventsUrl = URL "https://api.github.com/repos/some-organization/infra-core/events", + whRepoAssigneesUrl = URL "https://api.github.com/repos/some-organization/infra-core/assignees{/user}", + whRepoBranchesUrl = URL "https://api.github.com/repos/some-organization/infra-core/branches{/branch}", + whRepoTagsUrl = URL "https://api.github.com/repos/some-organization/infra-core/tags", + whRepoBlobsUrl = URL "https://api.github.com/repos/some-organization/infra-core/git/blobs{/sha}", + whRepoGitTagsUrl = URL "https://api.github.com/repos/some-organization/infra-core/git/tags{/sha}", + whRepoGitRefsUrl = URL "https://api.github.com/repos/some-organization/infra-core/git/refs{/sha}", + whRepoTreesUrl = URL "https://api.github.com/repos/some-organization/infra-core/git/trees{/sha}", + whRepoStatusesUrl = URL "https://api.github.com/repos/some-organization/infra-core/statuses/{sha}", + whRepoLanguagesUrl = URL "https://api.github.com/repos/some-organization/infra-core/languages", + whRepoStargazersUrl = URL "https://api.github.com/repos/some-organization/infra-core/stargazers", + whRepoContributorsUrl = URL "https://api.github.com/repos/some-organization/infra-core/contributors", + whRepoSubscribersUrl = URL "https://api.github.com/repos/some-organization/infra-core/subscribers", + whRepoSubscriptionUrl = URL "https://api.github.com/repos/some-organization/infra-core/subscription", + whRepoCommitsUrl = URL "https://api.github.com/repos/some-organization/infra-core/commits{/sha}", + whRepoGitCommitsUrl = URL "https://api.github.com/repos/some-organization/infra-core/git/commits{/sha}", + whRepoCommentsUrl = URL "https://api.github.com/repos/some-organization/infra-core/comments{/number}", + whRepoIssueCommentsUrl = URL "https://api.github.com/repos/some-organization/infra-core/issues/comments{/number}", + whRepoContentsUrl = URL "https://api.github.com/repos/some-organization/infra-core/contents/{+path}", + whRepoCompareUrl = URL "https://api.github.com/repos/some-organization/infra-core/compare/{base}...{head}", + whRepoMergesUrl = URL "https://api.github.com/repos/some-organization/infra-core/merges", + whRepoArchiveUrl = URL "https://api.github.com/repos/some-organization/infra-core/{archive_format}{/ref}", + whRepoDownloadsUrl = URL "https://api.github.com/repos/some-organization/infra-core/downloads", + whRepoIssuesUrl = URL "https://api.github.com/repos/some-organization/infra-core/issues{/number}", + whRepoPullsUrl = URL "https://api.github.com/repos/some-organization/infra-core/pulls{/number}", + whRepoMilestonesUrl = URL "https://api.github.com/repos/some-organization/infra-core/milestones{/number}", + whRepoNotificationsUrl = URL "https://api.github.com/repos/some-organization/infra-core/notifications{?since,all,participating}", + whRepoLabelsUrl = URL "https://api.github.com/repos/some-organization/infra-core/labels{/name}", + whRepoReleasesUrl = URL "https://api.github.com/repos/some-organization/infra-core/releases{/id}", + whRepoCreatedAt = read "2021-04-11 18:30:22Z", + whRepoUpdatedAt = read "2022-11-07 17:04:28Z", + whRepoPushedAt = read "2022-12-05 17:41:57Z", + whRepoGitUrl = URL "git://github.com/some-organization/infra-core.git", + whRepoSshUrl = URL "git@github.com:some-organization/infra-core.git", + whRepoCloneUrl = URL "https://github.com/some-organization/infra-core.git", + whRepoSvnUrl = URL "https://github.com/some-organization/infra-core", + whRepoHomepage = Just (URL "https://github.com/orgs/some-organization/projects/7"), + whRepoSize = 23932, + whRepoStargazersCount = 4, + whRepoWatchersCount = 4, + whRepoLanguage = Just "Brainfuck", + whRepoHasIssues = True, + whRepoHasDownloads = True, + whRepoHasWiki = True, + whRepoHasPages = False, + whRepoForkCount = 1, + whRepoMirrorUrl = Nothing, + whRepoOpenIssuesCount = 130, + whRepoDefaultBranchName = "main" + }, + evPushOrganization = + Just + ( HookOrganization + { whOrgLogin = "some-organization", + whOrgId = 52709322, + whOrgNodeId = "MDEyO9kZyFua2pXhGlvdjbzNcMzOTYz3", + whOrgUrl = URL "https://api.github.com/orgs/some-organization", + whOrgReposUrl = URL "https://api.github.com/orgs/some-organization/repos", + whOrgEventsUrl = URL "https://api.github.com/orgs/some-organization/events", + whOrgHooksUrl = Just (URL "https://api.github.com/orgs/some-organization/hooks"), + whOrgIssuesUrl = Just (URL "https://api.github.com/orgs/some-organization/issues"), + whOrgMembersUrl = URL "https://api.github.com/orgs/some-organization/members{/member}", + whOrgPublicMembersUrl = URL "https://api.github.com/orgs/some-organization/public_members{/member}", + whOrgAvatarUrl = URL "https://avatars.githubusercontent.com/u/52709322?v=4", + whOrgDescription = "Plurality Media" + } + ), + evPushSender = Nothing + } + releaseEventFixture :: ReleaseEvent releaseEventFixture = ReleaseEvent { evReleaseEventAction = ReleasePublishedAction diff --git a/src/GitHub/Data/Webhooks/Events.hs b/src/GitHub/Data/Webhooks/Events.hs index 570d314..f4a132d 100644 --- a/src/GitHub/Data/Webhooks/Events.hs +++ b/src/GitHub/Data/Webhooks/Events.hs @@ -9,6 +9,7 @@ This module contains types that represent GitHub webhook's events. -} module GitHub.Data.Webhooks.Events ( EventHasSender(..) + , EventHasMaybeSender(..) , EventHasRepo(..) -- , CheckSuiteEventAction(..) @@ -131,6 +132,11 @@ class EventHasSender eventKind where -- | Provides the sender context of a Webhook event. senderOfEvent :: eventKind -> HookUser +-- | Represents an event that may contain its sender information. +class EventHasMaybeSender eventKind where + -- | Provides the sender context of a Webhook event. + maybeSenderOfEvent :: eventKind -> Maybe HookUser + -- | Represents an event that contains its repository information. class EventHasRepo eventKind where -- | Provides the repository context of a Webhook event. @@ -1111,11 +1117,12 @@ data PushEvent = PushEvent , evPushHeadCommit :: !(Maybe HookCommit) , evPushRepository :: !HookRepository , evPushOrganization :: !(Maybe HookOrganization) - , evPushSender :: !HookUser + -- | In very rare cases, the sender may be missing. + , evPushSender :: !(Maybe HookUser) } deriving (Eq, Show, Typeable, Data, Generic) -instance EventHasSender PushEvent where senderOfEvent = evPushSender +instance EventHasMaybeSender PushEvent where maybeSenderOfEvent = evPushSender instance EventHasRepo PushEvent where repoForEvent = evPushRepository instance NFData PushEvent where rnf = genericRnf @@ -1567,7 +1574,7 @@ instance FromJSON PushEvent where <*> o .:? "head_commit" <*> o .: "repository" <*> o .:? "organization" - <*> o .: "sender" + <*> o .:? "sender" instance FromJSON ReleaseEvent where parseJSON = withObject "ReleaseEvent" $ \o -> ReleaseEvent