packages feed

tricorder-0.2.0.1: src/Tricorder/SourceLookup/Hackage.hs

module Tricorder.SourceLookup.Hackage
    ( Hackage (..)
    , Result (..)
    , fetchPackage
    , run
    ) where

import Atelier.Effects.Log (Log)
import Effectful (Effect, IOE)
import Effectful.Dispatch.Dynamic (interpret_)
import Effectful.Exception (catch)
import Effectful.TH (makeEffect)
import Network.HTTP.Req
    ( GET (..)
    , HttpException
    , NoReqBody (..)
    , Scheme (..)
    , Url
    , bsResponse
    , defaultHttpConfig
    , https
    , renderUrl
    , req
    , responseBody
    , responseStatusCode
    , responseStatusMessage
    , runReq
    , (/:)
    )

import Atelier.Effects.Log qualified as Log

import Tricorder.Module (PackageId, unPackageId)


data Hackage :: Effect where
    FetchPackage :: PackageId -> Hackage m Result


data Result
    = NotFound
    | Failure Text
    | Success ByteString


makeEffect ''Hackage


run :: (IOE :> es, Log :> es) => Eff (Hackage : es) a -> Eff es a
run = interpret_ \case
    FetchPackage packageId -> do
        let url = packageUrl packageId
        Log.debug $ "Fetching sdist from " <> renderUrl url
        result <-
            flip catch (pure . Left @HttpException) . fmap Right
                $ liftIO
                $ runReq defaultHttpConfig
                $ req
                    GET
                    (url)
                    NoReqBody
                    bsResponse
                    mempty
        case result of
            Left ex -> do
                pure
                    $ Failure
                    $ "Failed to fetch "
                        <> unPackageId packageId
                        <> " from "
                        <> show url
                        <> "\n"
                        <> show ex
            Right response -> do
                let statusCode = responseStatusCode response
                if
                    | statusCode >= 200 && statusCode < 300 ->
                        pure $ Success $ responseBody response
                    | statusCode == 404 ->
                        pure NotFound
                    | otherwise -> do
                        pure $ Failure $ show (responseStatusCode response) <> ": " <> decodeUtf8 (responseStatusMessage response)


packageUrl :: PackageId -> Url 'Https
packageUrl packageId =
    https "hackage.haskell.org"
        /: "package"
        /: unPackageId packageId
        /: unPackageId packageId <> ".tar.gz"