packages feed

github-post-receive 1.0.2.0 → 1.1.0.0

raw patch · 3 files changed

+398/−65 lines, 3 filesdep +containersdep +wai-loggerdep −conduitdep ~waidep ~warpPVP ok

version bump matches the API change (PVP)

Dependencies added: containers, wai-logger

Dependencies removed: conduit

Dependency ranges changed: wai, warp

API changes (from Hackage documentation)

- Github.PostReceive.Types: Payload :: Text -> Text -> Text -> Bool -> Bool -> Bool -> Text -> [Commit] -> Commit -> Repository -> User -> Payload
- Github.PostReceive.Types: payloadAfter :: Payload -> Text
- Github.PostReceive.Types: payloadBefore :: Payload -> Text
- Github.PostReceive.Types: payloadCommits :: Payload -> [Commit]
- Github.PostReceive.Types: payloadCompare :: Payload -> Text
- Github.PostReceive.Types: payloadCreated :: Payload -> Bool
- Github.PostReceive.Types: payloadDeleted :: Payload -> Bool
- Github.PostReceive.Types: payloadForced :: Payload -> Bool
- Github.PostReceive.Types: payloadHeadCommit :: Payload -> Commit
- Github.PostReceive.Types: payloadPusher :: Payload -> User
- Github.PostReceive.Types: payloadRef :: Payload -> Text
- Github.PostReceive.Types: payloadRepository :: Payload -> Repository
- Github.PostReceive.Types: userEmail :: User -> Maybe EmailAddress
- Github.PostReceive.Types: userName :: User -> Text
- Github.PostReceive.Types: userUsername :: User -> Maybe Text
+ Github.PostReceive.Server: app :: ApacheLogger -> Map ByteString (Payload -> IO ()) -> Application
+ Github.PostReceive.Types: Branch :: Text -> SimpleCommit -> Branch
+ Github.PostReceive.Types: L :: a -> Or a b
+ Github.PostReceive.Types: Push :: PushEvent -> Payload
+ Github.PostReceive.Types: PushEvent :: Text -> Text -> Text -> Bool -> Bool -> Bool -> Maybe Text -> Url -> [Commit] -> Commit -> Repository -> SimpleUser -> User -> PushEvent
+ Github.PostReceive.Types: R :: b -> Or a b
+ Github.PostReceive.Types: SimpleCommit :: Text -> Url -> Maybe Url -> SimpleCommit
+ Github.PostReceive.Types: SimpleStatusCommit :: SimpleUser -> SimpleUser -> Text -> Tree -> Url -> Int -> SimpleStatusCommit
+ Github.PostReceive.Types: SimpleUser :: Text -> Maybe EmailAddress -> Maybe Text -> Maybe Text -> SimpleUser
+ Github.PostReceive.Types: Status :: StatusEvent -> Payload
+ Github.PostReceive.Types: StatusCommit :: Text -> SimpleStatusCommit -> Url -> Url -> Url -> User -> User -> [SimpleCommit] -> StatusCommit
+ Github.PostReceive.Types: StatusEvent :: Int -> Text -> Text -> Url -> Text -> Text -> Text -> StatusCommit -> [Branch] -> Text -> Text -> Repository -> User -> StatusEvent
+ Github.PostReceive.Types: Tree :: Text -> Url -> Tree
+ Github.PostReceive.Types: branchCommit :: Branch -> SimpleCommit
+ Github.PostReceive.Types: branchName :: Branch -> Text
+ Github.PostReceive.Types: data Branch
+ Github.PostReceive.Types: data Or a b
+ Github.PostReceive.Types: data PushEvent
+ Github.PostReceive.Types: data SimpleCommit
+ Github.PostReceive.Types: data SimpleStatusCommit
+ Github.PostReceive.Types: data SimpleUser
+ Github.PostReceive.Types: data StatusCommit
+ Github.PostReceive.Types: data StatusEvent
+ Github.PostReceive.Types: data Tree
+ Github.PostReceive.Types: instance (Eq a, Eq b) => Eq (Or a b)
+ Github.PostReceive.Types: instance (FromJSON a, FromJSON b) => FromJSON (Or a b)
+ Github.PostReceive.Types: instance (Show a, Show b) => Show (Or a b)
+ Github.PostReceive.Types: instance Eq Branch
+ Github.PostReceive.Types: instance Eq PushEvent
+ Github.PostReceive.Types: instance Eq SimpleCommit
+ Github.PostReceive.Types: instance Eq SimpleStatusCommit
+ Github.PostReceive.Types: instance Eq SimpleUser
+ Github.PostReceive.Types: instance Eq StatusCommit
+ Github.PostReceive.Types: instance Eq StatusEvent
+ Github.PostReceive.Types: instance Eq Tree
+ Github.PostReceive.Types: instance FromJSON Branch
+ Github.PostReceive.Types: instance FromJSON PushEvent
+ Github.PostReceive.Types: instance FromJSON SimpleCommit
+ Github.PostReceive.Types: instance FromJSON SimpleStatusCommit
+ Github.PostReceive.Types: instance FromJSON SimpleUser
+ Github.PostReceive.Types: instance FromJSON StatusCommit
+ Github.PostReceive.Types: instance FromJSON StatusEvent
+ Github.PostReceive.Types: instance FromJSON Tree
+ Github.PostReceive.Types: instance Show Branch
+ Github.PostReceive.Types: instance Show PushEvent
+ Github.PostReceive.Types: instance Show SimpleCommit
+ Github.PostReceive.Types: instance Show SimpleStatusCommit
+ Github.PostReceive.Types: instance Show SimpleUser
+ Github.PostReceive.Types: instance Show StatusCommit
+ Github.PostReceive.Types: instance Show StatusEvent
+ Github.PostReceive.Types: instance Show Tree
+ Github.PostReceive.Types: instance Typeable Branch
+ Github.PostReceive.Types: instance Typeable Or
+ Github.PostReceive.Types: instance Typeable PushEvent
+ Github.PostReceive.Types: instance Typeable SimpleCommit
+ Github.PostReceive.Types: instance Typeable SimpleStatusCommit
+ Github.PostReceive.Types: instance Typeable SimpleUser
+ Github.PostReceive.Types: instance Typeable StatusCommit
+ Github.PostReceive.Types: instance Typeable StatusEvent
+ Github.PostReceive.Types: instance Typeable Tree
+ Github.PostReceive.Types: pushEventAfter :: PushEvent -> Text
+ Github.PostReceive.Types: pushEventBaseRef :: PushEvent -> Maybe Text
+ Github.PostReceive.Types: pushEventBefore :: PushEvent -> Text
+ Github.PostReceive.Types: pushEventCommits :: PushEvent -> [Commit]
+ Github.PostReceive.Types: pushEventCompare :: PushEvent -> Url
+ Github.PostReceive.Types: pushEventCreated :: PushEvent -> Bool
+ Github.PostReceive.Types: pushEventDeleted :: PushEvent -> Bool
+ Github.PostReceive.Types: pushEventForced :: PushEvent -> Bool
+ Github.PostReceive.Types: pushEventHeadCommit :: PushEvent -> Commit
+ Github.PostReceive.Types: pushEventPusher :: PushEvent -> SimpleUser
+ Github.PostReceive.Types: pushEventRef :: PushEvent -> Text
+ Github.PostReceive.Types: pushEventRepository :: PushEvent -> Repository
+ Github.PostReceive.Types: pushEventSender :: PushEvent -> User
+ Github.PostReceive.Types: repoArchiveUrl :: Repository -> Url
+ Github.PostReceive.Types: repoAssigneesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoBlobsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoBranchesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoCloneUrl :: Repository -> Url
+ Github.PostReceive.Types: repoCollaboratorsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoCommitsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoCompareUrl :: Repository -> Url
+ Github.PostReceive.Types: repoContentsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoContributorsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoDownloadsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoEventsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoForksCount :: Repository -> Int
+ Github.PostReceive.Types: repoForksUrl :: Repository -> Url
+ Github.PostReceive.Types: repoFullName :: Repository -> Text
+ Github.PostReceive.Types: repoGitCommitsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoGitRefsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoGitTagsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoGitUrl :: Repository -> Url
+ Github.PostReceive.Types: repoHasPages :: Repository -> Bool
+ Github.PostReceive.Types: repoHooksUrl :: Repository -> Url
+ Github.PostReceive.Types: repoHtmlUrl :: Repository -> Url
+ Github.PostReceive.Types: repoIssueCommentUrl :: Repository -> Url
+ Github.PostReceive.Types: repoIssueEventsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoIssuesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoKeysUrl :: Repository -> Url
+ Github.PostReceive.Types: repoLabelsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoLanguagesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoMergesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoMilestonesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoMirrorUrl :: Repository -> Maybe Url
+ Github.PostReceive.Types: repoNotificationsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoOpenIssuesCount :: Repository -> Int
+ Github.PostReceive.Types: repoPullsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoReleasesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoSshUrl :: Repository -> Url
+ Github.PostReceive.Types: repoStargazersCount :: Repository -> Int
+ Github.PostReceive.Types: repoStargazersUrl :: Repository -> Url
+ Github.PostReceive.Types: repoStatusesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoSubscribersUrl :: Repository -> Url
+ Github.PostReceive.Types: repoSubscriptionUrl :: Repository -> Url
+ Github.PostReceive.Types: repoSvnUrl :: Repository -> Url
+ Github.PostReceive.Types: repoTagsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoTeamsUrl :: Repository -> Url
+ Github.PostReceive.Types: repoTreesUrl :: Repository -> Url
+ Github.PostReceive.Types: repoUpdatedAt :: Repository -> Text
+ Github.PostReceive.Types: repoWatchersCount :: Repository -> Int
+ Github.PostReceive.Types: simpleCommitHtmlUrl :: SimpleCommit -> Maybe Url
+ Github.PostReceive.Types: simpleCommitSha :: SimpleCommit -> Text
+ Github.PostReceive.Types: simpleCommitUrl :: SimpleCommit -> Url
+ Github.PostReceive.Types: simpleStatusCommitAuthor :: SimpleStatusCommit -> SimpleUser
+ Github.PostReceive.Types: simpleStatusCommitCommentCount :: SimpleStatusCommit -> Int
+ Github.PostReceive.Types: simpleStatusCommitCommitter :: SimpleStatusCommit -> SimpleUser
+ Github.PostReceive.Types: simpleStatusCommitMessage :: SimpleStatusCommit -> Text
+ Github.PostReceive.Types: simpleStatusCommitTree :: SimpleStatusCommit -> Tree
+ Github.PostReceive.Types: simpleStatusCommitUrl :: SimpleStatusCommit -> Url
+ Github.PostReceive.Types: simpleUserDate :: SimpleUser -> Maybe Text
+ Github.PostReceive.Types: simpleUserEmail :: SimpleUser -> Maybe EmailAddress
+ Github.PostReceive.Types: simpleUserName :: SimpleUser -> Text
+ Github.PostReceive.Types: simpleUserUsername :: SimpleUser -> Maybe Text
+ Github.PostReceive.Types: statusCommitAuthor :: StatusCommit -> User
+ Github.PostReceive.Types: statusCommitCommentsUrl :: StatusCommit -> Url
+ Github.PostReceive.Types: statusCommitCommit :: StatusCommit -> SimpleStatusCommit
+ Github.PostReceive.Types: statusCommitCommitter :: StatusCommit -> User
+ Github.PostReceive.Types: statusCommitHtmlUrl :: StatusCommit -> Url
+ Github.PostReceive.Types: statusCommitParents :: StatusCommit -> [SimpleCommit]
+ Github.PostReceive.Types: statusCommitSHA :: StatusCommit -> Text
+ Github.PostReceive.Types: statusCommitUrl :: StatusCommit -> Url
+ Github.PostReceive.Types: statusEventBranches :: StatusEvent -> [Branch]
+ Github.PostReceive.Types: statusEventCommit :: StatusEvent -> StatusCommit
+ Github.PostReceive.Types: statusEventContext :: StatusEvent -> Text
+ Github.PostReceive.Types: statusEventCreatedAt :: StatusEvent -> Text
+ Github.PostReceive.Types: statusEventDescription :: StatusEvent -> Text
+ Github.PostReceive.Types: statusEventId :: StatusEvent -> Int
+ Github.PostReceive.Types: statusEventName :: StatusEvent -> Text
+ Github.PostReceive.Types: statusEventRepository :: StatusEvent -> Repository
+ Github.PostReceive.Types: statusEventSHA :: StatusEvent -> Text
+ Github.PostReceive.Types: statusEventSender :: StatusEvent -> User
+ Github.PostReceive.Types: statusEventState :: StatusEvent -> Text
+ Github.PostReceive.Types: statusEventTargetUrl :: StatusEvent -> Url
+ Github.PostReceive.Types: statusEventUpdatedAt :: StatusEvent -> Text
+ Github.PostReceive.Types: toEither :: Or a b -> Either a b
+ Github.PostReceive.Types: treeSHA :: Tree -> Text
+ Github.PostReceive.Types: treeUrl :: Tree -> Url
+ Github.PostReceive.Types: userAvatarUrl :: User -> Url
+ Github.PostReceive.Types: userEventsUrl :: User -> Url
+ Github.PostReceive.Types: userFollowersUrl :: User -> Url
+ Github.PostReceive.Types: userFollowingUrl :: User -> Url
+ Github.PostReceive.Types: userGistsUrl :: User -> Url
+ Github.PostReceive.Types: userGravatarId :: User -> Text
+ Github.PostReceive.Types: userHtmlUrl :: User -> Url
+ Github.PostReceive.Types: userId :: User -> Int
+ Github.PostReceive.Types: userLogin :: User -> Text
+ Github.PostReceive.Types: userOrganizationsUrl :: User -> Url
+ Github.PostReceive.Types: userReceivedEventsUrl :: User -> Url
+ Github.PostReceive.Types: userReposUrl :: User -> Url
+ Github.PostReceive.Types: userSiteAdmin :: User -> Bool
+ Github.PostReceive.Types: userStarredUrl :: User -> Url
+ Github.PostReceive.Types: userSubscriptionsUrl :: User -> Url
+ Github.PostReceive.Types: userType :: User -> Text
+ Github.PostReceive.Types: userUrl :: User -> Url
- Github.PostReceive.Server: start :: Port -> (Payload -> IO ()) -> IO ()
+ Github.PostReceive.Server: start :: Port -> Map ByteString (Payload -> IO ()) -> IO ()
- Github.PostReceive.Types: Commit :: Text -> Bool -> Text -> Text -> Text -> User -> User -> [FilePath] -> [FilePath] -> [FilePath] -> Commit
+ Github.PostReceive.Types: Commit :: Text -> Bool -> Text -> Text -> Url -> SimpleUser -> SimpleUser -> [FilePath] -> [FilePath] -> [FilePath] -> Commit
- Github.PostReceive.Types: Repository :: Int -> Text -> Text -> Text -> Maybe Text -> Int -> Int -> Int -> Bool -> Int -> User -> Bool -> Int -> Bool -> Bool -> Bool -> Text -> Int -> Int -> Text -> Repository
+ Github.PostReceive.Types: Repository :: Int -> Text -> Text -> Or SimpleUser User -> Bool -> Url -> Text -> Bool -> Text -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Or Int Text -> Text -> Or Int Text -> Url -> Url -> Url -> Url -> Maybe Url -> Int -> Int -> Int -> Text -> Bool -> Bool -> Bool -> Bool -> Int -> Maybe Url -> Int -> Int -> Int -> Int -> Maybe Int -> Maybe Text -> Repository
- Github.PostReceive.Types: User :: Text -> Maybe EmailAddress -> Maybe Text -> User
+ Github.PostReceive.Types: User :: Text -> Int -> Url -> Text -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Url -> Text -> Bool -> User
- Github.PostReceive.Types: commitAuthor :: Commit -> User
+ Github.PostReceive.Types: commitAuthor :: Commit -> SimpleUser
- Github.PostReceive.Types: commitCommitter :: Commit -> User
+ Github.PostReceive.Types: commitCommitter :: Commit -> SimpleUser
- Github.PostReceive.Types: commitUrl :: Commit -> Text
+ Github.PostReceive.Types: commitUrl :: Commit -> Url
- Github.PostReceive.Types: repoCreatedAt :: Repository -> Int
+ Github.PostReceive.Types: repoCreatedAt :: Repository -> Or Int Text
- Github.PostReceive.Types: repoHomepage :: Repository -> Maybe Text
+ Github.PostReceive.Types: repoHomepage :: Repository -> Maybe Url
- Github.PostReceive.Types: repoMasterBranch :: Repository -> Text
+ Github.PostReceive.Types: repoMasterBranch :: Repository -> Maybe Text
- Github.PostReceive.Types: repoOwner :: Repository -> User
+ Github.PostReceive.Types: repoOwner :: Repository -> Or SimpleUser User
- Github.PostReceive.Types: repoPushedAt :: Repository -> Int
+ Github.PostReceive.Types: repoPushedAt :: Repository -> Or Int Text
- Github.PostReceive.Types: repoStargazers :: Repository -> Int
+ Github.PostReceive.Types: repoStargazers :: Repository -> Maybe Int

Files

github-post-receive.cabal view
@@ -1,7 +1,13 @@ name:                github-post-receive-version:             1.0.2.0-synopsis:            Github post-receive server library-description:         This is a library to construct github post-receive servers.+version:             1.1.0.0+synopsis:            GitHub webhooks library+description:         This is a library to construct github webhooks servers.+                     .+                     Currently, this library supports following events:+                     .+                     * push+                     .+                     * status homepage:            https://github.com/amutake/github-post-receive license:             BSD3 license-file:        LICENSE@@ -24,11 +30,12 @@   build-depends:     base ==4.*                    , bytestring >=0.10                    , text >=0.11+                   , containers                    , aeson >=0.6.1                    , email-validate >=1.0-                   , conduit >=1.0                    , http-types >=0.8-                   , wai ==2.*-                   , warp ==2.*+                   , wai ==3.*+                   , warp >=3.0.1 && < 4+                   , wai-logger ==2.*   ghc-options:       -Wall -fno-warn-orphans -fno-warn-unused-do-bind   default-language:  Haskell2010
src/Github/PostReceive/Server.hs view
@@ -1,22 +1,61 @@+{-# LANGUAGE OverloadedStrings #-}+ module Github.PostReceive.Server     ( start+    , app     ) where  import Control.Applicative ((<$>))-import Data.Aeson (decode)+import Data.Aeson (eitherDecode) import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL-import Data.Conduit (($$))-import qualified Data.Conduit.List as CL-import Network.HTTP.Types (ok200, badRequest400, urlDecode)-import Network.Wai (requestBody, responseLBS)+import qualified Data.Map as M+import Network.HTTP.Types+    ( ok200, badRequest400, notFound404, internalServerError500+    , urlDecode, methodPost, hContentType+    )+import Network.Wai+    ( strictRequestBody, responseLBS, Application+    , rawPathInfo, requestMethod, requestHeaders+    ) import Network.Wai.Handler.Warp (run, Port)+import Network.Wai.Logger (withStdoutLogger, ApacheLogger)  import Github.PostReceive.Types (Payload) -start :: Port -> (Payload -> IO ()) -> IO ()-start port cont = run port $ \req -> do-    bs <- B.drop (length "payload=") . B.concat <$> (requestBody req $$ CL.consume)-    case decode $ BL.fromStrict $ urlDecode True bs of-        Nothing -> return (responseLBS badRequest400 [] BL.empty)-        Just payload -> cont payload >> return (responseLBS ok200 [] BL.empty)+start :: Port -> M.Map B.ByteString (Payload -> IO ()) -> IO ()+start port routes = do+    putStrLn startingMessage+    withStdoutLogger $ run port . flip app routes+  where+    startingMessage = concat+        [ "github-post-receive listening on port "+        , show port+        , " with path "+        , show $ M.keys routes+        ]++app :: ApacheLogger -> M.Map B.ByteString (Payload -> IO ()) -> Application+app aplogger routes req respond+    | method == methodPost = flip (maybe notFound) (M.lookup path routes) $ \cont ->+        case contentType of+            Just "application/json" -> jsonCase cont+            Just "application/x-www-form-urlencoded" -> formCase cont+            _ -> badRequest+    | otherwise = notFound+  where+    path = rawPathInfo req+    method = requestMethod req+    contentType = lookup hContentType $ requestHeaders req+    res status = aplogger req status Nothing >> respond (responseLBS status [] BL.empty)+    notFound = res notFound404+    badRequest = res badRequest400+    internalError reason = putStrLn reason >> res internalServerError500+    ok = res ok200+    jsonCase cont = do+        bs <- strictRequestBody req+        flip (either internalError) (eitherDecode bs) $ \payload -> cont payload >> ok+    formCase cont = do+        bs <- BL.drop (BL.length "payload=") <$> strictRequestBody req+        flip (either internalError) (eitherDecode $ BL.fromStrict $ urlDecode True $ BL.toStrict bs) $ \payload ->+            cont payload >> ok
src/Github/PostReceive/Types.hs view
@@ -1,15 +1,26 @@ {-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}  module Github.PostReceive.Types     ( Payload (..)+    , PushEvent (..)+    , StatusEvent (..)     , Commit (..)     , Repository (..)     , User (..)+    , SimpleUser (..)+    , Branch (..)+    , SimpleCommit (..)+    , StatusCommit (..)+    , SimpleStatusCommit (..)+    , Tree (..)+    , Or (..)+    , toEither       -- Re-exports     , EmailAddress     ) where -import Control.Applicative ((<$>), (<*>), pure)+import Control.Applicative ((<$>), (<*>), pure, (<|>)) import Data.Aeson (Value (..), FromJSON (..), (.:), (.:?)) import qualified Data.ByteString.Char8 as B import Data.Text (Text)@@ -17,43 +28,88 @@ import Data.Typeable (Typeable) import Text.Email.Validate (EmailAddress, emailAddress) -data Payload = Payload-    { payloadRef :: Text-    , payloadAfter :: Text-    , payloadBefore :: Text-    , payloadCreated :: Bool-    , payloadDeleted :: Bool-    , payloadForced :: Bool-    , payloadCompare :: Text-    , payloadCommits :: [Commit]-    , payloadHeadCommit :: Commit-    , payloadRepository :: Repository-    , payloadPusher :: User-    } deriving (Show, Eq, Typeable)+data Payload = Push PushEvent+             | Status StatusEvent+             deriving (Show, Eq, Typeable)  instance FromJSON Payload where-    parseJSON (Object o) = Payload+    parseJSON v = Push <$> parseJSON v+              <|> Status <$> parseJSON v++data PushEvent = PushEvent+    { pushEventRef :: Text+    , pushEventBefore :: Text+    , pushEventAfter :: Text+    , pushEventCreated :: Bool+    , pushEventDeleted :: Bool+    , pushEventForced :: Bool+    , pushEventBaseRef :: Maybe Text+    , pushEventCompare :: Url+    , pushEventCommits :: [Commit]+    , pushEventHeadCommit :: Commit+    , pushEventRepository :: Repository+    , pushEventPusher :: SimpleUser+    , pushEventSender :: User+    } deriving (Show, Eq, Typeable)++instance FromJSON PushEvent where+    parseJSON (Object o) = PushEvent         <$> o .: "ref"-        <*> o .: "after"         <*> o .: "before"+        <*> o .: "after"         <*> o .: "created"         <*> o .: "deleted"         <*> o .: "forced"+        <*> o .:? "base_ref"         <*> o .: "compare"         <*> o .: "commits"         <*> o .: "head_commit"         <*> o .: "repository"         <*> o .: "pusher"-    parseJSON _ = fail "Payload must be an object"+        <*> o .: "sender"+    parseJSON _ = fail "PushEvent must be an object" +data StatusEvent = StatusEvent+    { statusEventId :: Int+    , statusEventSHA :: Text+    , statusEventName :: Text+    , statusEventTargetUrl :: Url+    , statusEventContext :: Text+    , statusEventDescription :: Text+    , statusEventState :: Text+    , statusEventCommit :: StatusCommit+    , statusEventBranches :: [Branch]+    , statusEventCreatedAt :: Text -- TODO: Change to date type+    , statusEventUpdatedAt :: Text -- TODO: Change to date type+    , statusEventRepository :: Repository+    , statusEventSender :: User+    } deriving (Show, Eq, Typeable)++instance FromJSON StatusEvent where+    parseJSON (Object o) = StatusEvent+        <$> o .: "id"+        <*> o .: "sha"+        <*> o .: "name"+        <*> o .: "target_url"+        <*> o .: "context"+        <*> o .: "description"+        <*> o .: "state"+        <*> o .: "commit"+        <*> o .: "branches"+        <*> o .: "created_at"+        <*> o .: "updated_at"+        <*> o .: "repository"+        <*> o .: "sender"+    parseJSON _ = fail "StatusEvent must be an object"+ data Commit = Commit     { commitId :: Text     , commitDistinct :: Bool     , commitMessage :: Text     , commitTimestamp :: Text-    , commitUrl :: Text-    , commitAuthor :: User-    , commitCommitter :: User+    , commitUrl :: Url+    , commitAuthor :: SimpleUser+    , commitCommitter :: SimpleUser     , commitAdded :: [FilePath]     , commitRemoved :: [FilePath]     , commitModified :: [FilePath]@@ -76,65 +132,296 @@ data Repository = Repository     { repoId :: Int     , repoName :: Text-    , repoUrl :: Text+    , repoFullName :: Text+    , repoOwner :: Or SimpleUser User+    , repoPrivate :: Bool+    , repoHtmlUrl :: Url     , repoDescription :: Text-    , repoHomepage :: Maybe Text-    , repoWatchers :: Int-    , repoStargazers :: Int-    , repoForks :: Int     , repoFork :: Bool+      -- urls+    , repoUrl :: Text+    , repoForksUrl :: Url+    , repoKeysUrl :: Url+    , repoCollaboratorsUrl :: Url+    , repoTeamsUrl :: Url+    , repoHooksUrl :: Url+    , repoIssueEventsUrl :: Url+    , repoEventsUrl :: Url+    , repoAssigneesUrl :: Url+    , repoBranchesUrl :: Url+    , repoTagsUrl :: Url+    , repoBlobsUrl :: Url+    , repoGitTagsUrl :: Url+    , repoGitRefsUrl :: Url+    , repoTreesUrl :: Url+    , repoStatusesUrl :: Url+    , repoLanguagesUrl :: Url+    , repoStargazersUrl :: Url+    , repoContributorsUrl :: Url+    , repoSubscribersUrl :: Url+    , repoSubscriptionUrl :: Url+    , repoCommitsUrl :: Url+    , repoGitCommitsUrl :: Url+    , repoIssueCommentUrl :: Url+    , repoContentsUrl :: Url+    , repoCompareUrl :: Url+    , repoMergesUrl :: Url+    , repoArchiveUrl :: Url+    , repoDownloadsUrl :: Url+    , repoIssuesUrl :: Url+    , repoPullsUrl :: Url+    , repoMilestonesUrl :: Url+    , repoNotificationsUrl :: Url+    , repoLabelsUrl :: Url+    , repoReleasesUrl :: Url+      -- date+    , repoCreatedAt :: Or Int Text -- Int or DateString+    , repoUpdatedAt :: Text+    , repoPushedAt :: Or Int Text -- Int or DateString+    , repoGitUrl :: Url+    , repoSshUrl :: Url+    , repoCloneUrl :: Url+    , repoSvnUrl :: Url+    , repoHomepage :: Maybe Url     , repoSize :: Int-    , repoOwner :: User-    , repoPrivate :: Bool-    , repoOpenIssues :: Int+    , repoStargazersCount :: Int+    , repoWatchersCount :: Int+    , repoLanguage :: Text     , repoHasIssues :: Bool     , repoHasDownloads :: Bool     , repoHasWiki :: Bool-    , repoLanguage :: Text-    , repoCreatedAt :: Int-    , repoPushedAt :: Int-    , repoMasterBranch :: Text+    , repoHasPages :: Bool+    , repoForksCount :: Int+    , repoMirrorUrl :: Maybe Url+    , repoOpenIssuesCount :: Int+      -- for compatiblity?+    , repoForks :: Int+    , repoOpenIssues :: Int+    , repoWatchers :: Int+    , repoStargazers :: Maybe Int+    , repoMasterBranch :: Maybe Text     } deriving (Show, Eq, Typeable)  instance FromJSON Repository where     parseJSON (Object o) = Repository         <$> o .: "id"         <*> o .: "name"-        <*> o .: "url"+        <*> o .: "full_name"+        <*> o .: "owner"+        <*> o .: "private"+        <*> o .: "html_url"         <*> o .: "description"-        <*> o .:? "homepage"-        <*> o .: "watchers"-        <*> o .: "stargazers"-        <*> o .: "forks"         <*> o .: "fork"+        <*> o .: "url"+        <*> o .: "forks_url"+        <*> o .: "keys_url"+        <*> o .: "collaborators_url"+        <*> o .: "teams_url"+        <*> o .: "hooks_url"+        <*> o .: "issue_events_url"+        <*> o .: "events_url"+        <*> o .: "assignees_url"+        <*> o .: "branches_url"+        <*> o .: "tags_url"+        <*> o .: "blobs_url"+        <*> o .: "git_tags_url"+        <*> o .: "git_refs_url"+        <*> o .: "trees_url"+        <*> o .: "statuses_url"+        <*> o .: "languages_url"+        <*> o .: "stargazers_url"+        <*> o .: "contributors_url"+        <*> o .: "subscribers_url"+        <*> o .: "subscription_url"+        <*> o .: "commits_url"+        <*> o .: "git_commits_url"+        <*> o .: "issue_comment_url"+        <*> o .: "contents_url"+        <*> o .: "compare_url"+        <*> o .: "merges_url"+        <*> o .: "archive_url"+        <*> o .: "downloads_url"+        <*> o .: "issues_url"+        <*> o .: "pulls_url"+        <*> o .: "milestones_url"+        <*> o .: "notifications_url"+        <*> o .: "labels_url"+        <*> o .: "releases_url"+        <*> o .: "created_at"+        <*> o .: "updated_at"+        <*> o .: "pushed_at"+        <*> o .: "git_url"+        <*> o .: "ssh_url"+        <*> o .: "clone_url"+        <*> o .: "svn_url"+        <*> o .:? "homepage"         <*> o .: "size"-        <*> o .: "owner"-        <*> o .: "private"-        <*> o .: "open_issues"+        <*> o .: "stargazers_count"+        <*> o .: "watchers_count"+        <*> o .: "language"         <*> o .: "has_issues"         <*> o .: "has_downloads"         <*> o .: "has_wiki"-        <*> o .: "language"-        <*> o .: "created_at"-        <*> o .: "pushed_at"-        <*> o .: "master_branch"+        <*> o .: "has_pages"+        <*> o .: "forks_count"+        <*> o .:? "mirror_url"+        <*> o .: "open_issues_count"+        <*> o .: "forks"+        <*> o .: "open_issues"+        <*> o .: "watchers"+        <*> o .:? "stargazers"+        <*> o .:? "master_branch"     parseJSON _ = fail "Repository must be an object"  data User = User-    { userName :: Text-    , userEmail :: Maybe EmailAddress-    , userUsername :: Maybe Text+    { userLogin :: Text+    , userId :: Int+    , userAvatarUrl :: Url+    , userGravatarId :: Text+    , userUrl :: Url+    , userHtmlUrl :: Url+    , userFollowersUrl :: Url+    , userFollowingUrl :: Url+    , userGistsUrl :: Url+    , userStarredUrl :: Url+    , userSubscriptionsUrl :: Url+    , userOrganizationsUrl :: Url+    , userReposUrl :: Url+    , userEventsUrl :: Url+    , userReceivedEventsUrl :: Url+    , userType :: Text+    , userSiteAdmin :: Bool     } deriving (Show, Eq, Typeable)  instance FromJSON User where     parseJSON (Object o) = User+        <$> o .: "login"+        <*> o .: "id"+        <*> o .: "avatar_url"+        <*> o .: "gravatar_id"+        <*> o .: "url"+        <*> o .: "html_url"+        <*> o .: "followers_url"+        <*> o .: "following_url"+        <*> o .: "gists_url"+        <*> o .: "starred_url"+        <*> o .: "subscriptions_url"+        <*> o .: "organizations_url"+        <*> o .: "repos_url"+        <*> o .: "events_url"+        <*> o .: "received_events_url"+        <*> o .: "type"+        <*> o .: "site_admin"+    parseJSON _ = fail "User must be an object"++data SimpleUser = SimpleUser+    { simpleUserName :: Text+    , simpleUserEmail :: Maybe EmailAddress+    , simpleUserUsername :: Maybe Text+    , simpleUserDate :: Maybe Text+    } deriving (Show, Eq, Typeable)++instance FromJSON SimpleUser where+    parseJSON (Object o) = SimpleUser         <$> o .: "name"         <*> o .:? "email"         <*> o .:? "username"-    parseJSON _ = fail "User must be an object"+        <*> o .:? "date"+    parseJSON _ = fail "SimpleUser must be an object"  instance FromJSON EmailAddress where     parseJSON (String t) = case emailAddress $ B.pack . T.unpack $ t of         Just a -> pure a         Nothing -> fail "failed to parse EmailAddress"     parseJSON _ = fail "EmailAddress must be a text"++data Branch = Branch+    { branchName :: Text+    , branchCommit :: SimpleCommit+    } deriving (Show, Eq, Typeable)++instance FromJSON Branch where+    parseJSON (Object o) = Branch+        <$> o .: "name"+        <*> o .: "commit"+    parseJSON _ = fail "Branch must be an object"++data SimpleCommit = SimpleCommit+    { simpleCommitSha :: Text+    , simpleCommitUrl :: Url+    , simpleCommitHtmlUrl :: Maybe Url+    } deriving (Show, Eq, Typeable)++instance FromJSON SimpleCommit where+    parseJSON (Object o) = SimpleCommit+        <$> o .: "sha"+        <*> o .: "url"+        <*> o .:? "html_url"+    parseJSON _ = fail "SimpleCommit must be an object"++-- | used in StatusEvent+data StatusCommit = StatusCommit+    { statusCommitSHA :: Text+    , statusCommitCommit :: SimpleStatusCommit+    , statusCommitUrl :: Url+    , statusCommitHtmlUrl :: Url+    , statusCommitCommentsUrl :: Url+    , statusCommitAuthor :: User+    , statusCommitCommitter :: User+    , statusCommitParents :: [SimpleCommit]+    } deriving (Show, Eq, Typeable)++instance FromJSON StatusCommit where+    parseJSON (Object o) = StatusCommit+        <$> o .: "sha"+        <*> o .: "commit"+        <*> o .: "url"+        <*> o .: "html_url"+        <*> o .: "comments_url"+        <*> o .: "author"+        <*> o .: "committer"+        <*> o .: "parents"+    parseJSON _ = fail "StatusCommit must be an object"++data SimpleStatusCommit = SimpleStatusCommit+    { simpleStatusCommitAuthor :: SimpleUser+    , simpleStatusCommitCommitter :: SimpleUser+    , simpleStatusCommitMessage :: Text+    , simpleStatusCommitTree :: Tree+    , simpleStatusCommitUrl :: Url+    , simpleStatusCommitCommentCount :: Int+    } deriving (Show, Eq, Typeable)++instance FromJSON SimpleStatusCommit where+    parseJSON (Object o) = SimpleStatusCommit+        <$> o .: "author"+        <*> o .: "committer"+        <*> o .: "message"+        <*> o .: "tree"+        <*> o .: "url"+        <*> o .: "comment_count"+    parseJSON _ = fail "SimpleStatusCommit must be an object"++data Tree = Tree+    { treeSHA :: Text+    , treeUrl :: Url+    } deriving (Show, Eq, Typeable)++instance FromJSON Tree where+    parseJSON (Object o) = Tree+        <$> o .: "sha"+        <*> o .: "url"+    parseJSON _ = fail "Tree must be an object"++type Url = Text++-- | Or a b represents a or b+-- The reason why we don't use Either type is that Either Int String type parses { "left": 1 } or { "right": "foo" }, but we want to parse 1 or "foo".+data Or a b = L a | R b deriving (Show, Eq, Typeable)++instance (FromJSON a, FromJSON b) => FromJSON (Or a b) where+    parseJSON v = L <$> parseJSON v <|> R <$> parseJSON v++toEither :: Or a b -> Either a b+toEither (L a) = Left a+toEither (R b) = Right b