hackage-api-0.1.0: src/Distribution/Hackage/Types.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StrictData #-}
module Distribution.Hackage.Types where
import Data.Aeson
import Data.ByteString.Lazy (toStrict)
import Data.Function
import Data.List.NonEmpty (toList)
import Data.Maybe
import Data.Proxy
import Data.Text (Text, unpack)
import Data.Time
import Distribution.PackageDescription
import Distribution.PackageDescription.Parsec
import Distribution.Parsec
import GHC.Generics (Generic)
import Network.HTTP.Media ((//), (/:))
import Servant.API
import Servant.API.ContentTypes
import Servant.Client
import Servant.Client.Core
data JSON0
instance Accept JSON0 where
contentType _ = "application/json"
instance FromJSON a => MimeUnrender JSON0 a where
mimeUnrender _ = eitherDecodeLenient
-----------------------------------------------------------
newtype Time = Time UTCTime
deriving newtype (Show, Eq)
instance FromJSON Time where
parseJSON = withText "time" $ \t ->
case parseTimeM False defaultTimeLocale (iso8601DateFormat (Just "%T%Z")) (unpack t) of
Just time -> pure $ Time time
Nothing -> fail "failed to parse time"
data Version
= Default
| Version Text
deriving (Show, Eq, Generic)
instance FromJSON Version where
parseJSON = withText "version" $ pure . Version
data Package = Package
{ packageName :: Text,
version :: Version
}
deriving (Show, Eq, Generic)
instance FromJSON Package where
parseJSON = withObject "package" $ \o -> do
packageName <- o .: "packageName"
let version = Default
pure $ Package {..}
instance ToHttpApiData Package where
toUrlPiece Package {..}
| Default <- version = packageName
| Version ver <- version = packageName <> "-" <> ver
data Revision = Revision
{ time :: Time,
user :: Text,
number :: Int
}
deriving (Show, Eq, Generic)
deriving (FromJSON)
data Versions = Versions
{ normal :: [Text],
unpreferred :: [Text],
deprecated :: [Text]
}
deriving (Show, Eq, Generic)
instance FromJSON Versions where
parseJSON = withObject "versions" $ \o -> do
let withDefault = fmap (fromMaybe [])
normal <- withDefault (o .:? "normal-version")
unpreferred <- withDefault (o .:? "unprefereed-version")
deprecated <- withDefault (o .:? "deprecated-version")
pure Versions {..}
data Cabal
instance Accept Cabal where
contentType _ = "text" // "plain" /: ("charset", "utf-8")
instance MimeUnrender Cabal GenericPackageDescription where
mimeUnrender _ f = r
where
res' = parseGenericPackageDescription $ toStrict f
(_, res) = runParseResult res'
showErrors es =
"cabal file parse failed with the following errors:\n"
<> unlines (map (showPError "") es)
r = case res of
Left (_, es') -> Left $ showErrors $ toList es'
Right desc -> Right desc
data CabalFile
instance HasClient m api => HasClient m (CabalFile :> api) where
type Client m (CabalFile :> api) = Package -> Client m api
clientWithRoute pm Proxy req = \p@Package {..} ->
clientWithRoute pm (Proxy @api) $
req
& appendToPath (toQueryParam p)
& appendToPath (packageName <> ".cabal")
hoistClientMonad pm Proxy f cl = hoistClientMonad pm (Proxy @api) f . cl
-----------------------------------------------------------
-- Servant API Types
type GetPackages = "packages" :> Get '[JSON0] [Package]
type GetVersions = "package" :> Capture "package" Package :> "preferred" :> Get '[JSON0] Versions
type GetRevisions = "package" :> Capture "package" Package :> "revisions" :> Get '[JSON0] [Revision]
type GetCabalFile = "package" :> CabalFile :> Get '[Cabal] GenericPackageDescription
type GetCabalFile' = "package" :> Capture "package" Package :> "revision" :> Capture "revision" Int :> Get '[Cabal] GenericPackageDescription
type HackageAPI =
GetPackages
:<|> GetVersions
:<|> GetRevisions
:<|> GetCabalFile
:<|> GetCabalFile'