{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module GitHub.REST.PageLinks ( PageLinks (..), parsePageLinks, ) where import Data.Maybe (fromMaybe) #if !MIN_VERSION_base(4,11,0) import Data.Semigroup (Semigroup(..)) #endif import Data.Text (Text) import qualified Data.Text as Text {- | Helper type for GitHub pagination. https://developer.github.com/v3/guides/traversing-with-pagination/ -} data PageLinks = PageLinks { pageFirst :: Maybe Text , pagePrev :: Maybe Text , pageNext :: Maybe Text , pageLast :: Maybe Text } deriving (Eq, Show) instance Semigroup PageLinks where links1 <> links2 = PageLinks (pageFirst links1 <> pageFirst links2) (pagePrev links1 <> pagePrev links2) (pageNext links1 <> pageNext links2) (pageLast links1 <> pageLast links2) instance Monoid PageLinks where mempty = PageLinks Nothing Nothing Nothing Nothing #if !MIN_VERSION_base(4,11,0) mappend = (<>) #endif parsePageLinks :: Text -> PageLinks parsePageLinks = foldl resolve mempty . split "," where resolve :: PageLinks -> Text -> PageLinks resolve pageLinks "" = pageLinks resolve pageLinks link = let (rel, url) = parsePageLink link in case rel of "first" -> pageLinks{pageFirst = Just url} "prev" -> pageLinks{pagePrev = Just url} "next" -> pageLinks{pageNext = Just url} "last" -> pageLinks{pageLast = Just url} _ -> error $ "Unknown rel in page link: " ++ show link {- | Parse a single page link, like: ; rel="next" Returns ("next", "/search/code?q=addClass+user%3Amozilla&page=2") -} parsePageLink :: Text -> (Text, Text) parsePageLink link = fromMaybe (error $ "Unknown page link: " ++ show link) $ do (linkUrl, linkRel) <- case split ";" link of [url, rel] -> pure (url, rel) _ -> mempty url <- Text.stripPrefix ghUrl $ dropAround "<" ">" linkUrl rel <- case split "=" linkRel of ["rel", linkRel'] -> pure $ dropAround "\"" "\"" linkRel' _ -> mempty pure (rel, url) where ghUrl = "https://api.github.com" {- Helpers -} -- | Split the given text by the given delimiter, stripping any surrounding whitespace. split :: Text -> Text -> [Text] split delim = map Text.strip . Text.splitOn delim -- | Drop the given strings at the beginning and end of the given text. dropAround :: Text -> Text -> Text -> Text dropAround begin end s = fromMaybe badDrop $ Text.stripSuffix end =<< Text.stripPrefix begin s where badDrop = error $ "Expected value to wrap within " ++ Text.unpack begin ++ "..." ++ Text.unpack end ++ ": " ++ Text.unpack s