Here is my Haskell program designed to list all of my GitHub repos along with their descriptions and languages via the GitHub JSON APIs:{-# LANGUAGE DeriveGeneric #-}{-# LANGUAGE OverloadedStrings #-}module Main whereimport Control.Monadimport Data.Aesonimport qualified Data.ByteString as BSimport qualified Data.ByteString.Char8 as C8import qualified Data.List as Limport Data.Maybeimport Data.Textimport qualified Data.Text.Encoding as Eimport Data.Word (Word16)import GHC.Generics (Generic)import Network.Http.Clientimport Network.HTTP.Linkimport Network.URIimport OpenSSLuriIsSsl :: URI -> BooluriIsSsl uri = uriScheme uri == https:uriGetHostName :: URI -> Maybe StringuriGetHostName uri = uriRegName <$> uriAuthority uriuriGetPort :: URI -> Word16 -> Maybe Word16uriGetPort uri defaultPort = do  auth <- uriAuthority uri  return $ case uriPort auth of                 -> defaultPort                p -> (Prelude.read $ Prelude.tail p) :: Word16uriGetFullPath :: URI -> StringuriGetFullPath uri = uriPath uri ++ uriQuery uri ++ uriFragment uricontainsLinkParam :: Link -> LinkParam -> Text -> BoolcontainsLinkParam link linkParam value =  isJust $ L.find (\(lp, v) -> lp == linkParam && v == value) $ linkParams linkhasRelNext :: Link -> BoolhasRelNext link = containsLinkParam link Rel nextfindNextLink :: BS.ByteString -> Maybe LinkfindNextLink value = do  links <- parseLinkHeader $ E.decodeUtf8 value  L.find hasRelNext linksgetLinkHeader :: Response -> Maybe BS.ByteStringgetLinkHeader p = getHeader p LinknextLinkFromResponse :: Response -> Maybe LinknextLinkFromResponse p = getLinkHeader p >>= findNextLinkopenUri :: URI -> (Connection -> BS.ByteString -> IO a) -> IO aopenUri uri f =  let    isSsl = uriIsSsl uri    hostName = C8.pack $ fromJust $ uriGetHostName uri    port = fromJust $ uriGetPort uri (if isSsl then 443 else 80)    fullPath = C8.pack $ uriGetFullPath uri    wrappedF c = f c fullPath  in    if isSsl       then          withOpenSSL $ do            ctx <- baselineContextSSL            withConnection (openConnectionSSL ctx hostName port) wrappedF       else          withConnection (openConnection hostName port) wrappedFdata Repo = Repo {    name :: String  , description :: String  , language :: Maybe String} deriving (Show, Generic)instance FromJSON RepofetchRepos :: URI -> IO [Repo]fetchRepos uri =  openUri uri $ \c fullPath -> do    request <- buildRequest $ do      http GET fullPath      setAccept application/json      setHeader User-Agent MyGitHubApiClient    sendRequest c request emptyBody    receiveResponse c $ \p i -> do      repos <- jsonHandler p i      nextRepos <- case nextLinkFromResponse p of                        Just link -> fetchRepos $ href link                        Nothing -> return []      return $ repos ++ nextReposmain :: IO ()main = do  repos <- fetchRepos $ fromJust $ parseURI https://api.github.com/users/rcook/repos  putStrLn $ show (Prelude.length repos) ++  repos:  forM_ repos $ \repo -> print repoThe trickiest thing I had to implement in this program was parsing and following Link headers in the HTTP response in order to deal with the API's built-in pagination behaviour. Fortunately, the http-link-header module exists so I didn't have to write the parser from scratch. However, figuring out where to follow the Rel=next links was challenging at first.I'm interested in hearing any constructive criticisms or suggestions, e.g.:Code that could be refactored into more idiomatic Haskell styleOutright bugsImprovements to error handlingIndentation!I'd also welcome any suggestions about how I'd write automated tests for this code, given that much of the code is in the IO monad.
GitHub API client
haskell;json;api;pagination;git