packages feed

trasa-client-0.2: test/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# OPTIONS_GHC -Wall -Werror -Wno-unticked-promoted-constructors #-}
module Main where

import Data.Kind (Type)
import GHC.Generics hiding (Meta)
import Data.Bifunctor (Bifunctor(..))
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import Data.Aeson
  (Value(..),FromJSON(..),ToJSON(..),encode,eitherDecode'
  ,object,withObject,(.:),(.=))
import Net.Types (IPv4)
import qualified Net.IPv4.String as IPv4
import Control.Exception (catch,SomeException)
import System.Exit (exitFailure)
import qualified Network.HTTP.Types.Status as N
import qualified Network.HTTP.Client as N
import Trasa.Core
import qualified Trasa.Method as M
import Trasa.Client

data Ip = Ip
  { origin :: IPv4
  } deriving (Generic,FromJSON,ToJSON)

instance Show Ip where
  show (Ip ipv4) = "{ origin: " ++ IPv4.encode ipv4 ++ " }"

data Args = Args
  { args :: H.HashMap T.Text Value
  } deriving Show

instance FromJSON Args where
  parseJSON = withObject "Args" $ \o -> Args <$> o .: "args"

instance ToJSON Args where
  toJSON (Args as) = object [ "args" .= as ]

bodyAeson :: (FromJSON a, ToJSON a) => BodyCodec a
bodyAeson = BodyCodec (pure "application/json") encode (first T.pack . eitherDecode')

int :: CaptureCodec Int
int = showReadCaptureCodec

bodyUnit :: BodyCodec ()
bodyUnit = BodyCodec (pure "text/html") (const "") (const (Right ()))

data Route :: [Type] -> [Param] -> Bodiedness -> Type -> Type where
  RouteHome :: Route '[] '[] Bodyless ()
  RouteIp :: Route '[] '[] Bodyless Ip
  RouteStatus :: Route '[Int] '[] Bodyless ()
  RouteQuery :: Route '[] '[Optional Int] Bodyless Args

data Meta caps qrys req resp = Meta
  { metaPath :: Path CaptureCodec caps
  , metaQuery :: Rec (Query CaptureCodec) qrys
  , metaRequestBody :: RequestBody BodyCodec req
  , metaResponseBody :: ResponseBody BodyCodec resp
  , metaMethod :: Method }

meta :: Route caps qrys req resp -> Meta caps qrys req resp
meta = \case
  RouteHome -> Meta end qend bodyless (resp bodyUnit) M.get
  RouteIp -> Meta (match "ip" ./ end) qend bodyless (resp bodyAeson) M.get
  RouteStatus -> Meta (match "status" ./ capture int ./ end) qend bodyless (resp bodyUnit) M.get
  RouteQuery -> Meta (match "anything" ./ end) (optional "int" int .& qend) bodyless (resp bodyAeson) M.get

prepare :: Route caps qrys req resp -> Arguments caps qrys req (Prepared Route resp)
prepare = prepareWith (metaPath . meta) (metaQuery . meta) (metaRequestBody . meta)

link :: Prepared Route resp -> Url
link = linkWith
  (mapPath captureCodecToCaptureEncoding . metaPath . meta)
  (mapQuery captureCodecToCaptureEncoding . metaQuery . meta)

client :: Config -> Prepared Route resp -> IO (Either TrasaErr resp)
client = clientWith
  (metaMethod . meta)
  (mapPath captureCodecToCaptureEncoding . metaPath . meta)
  (mapQuery captureCodecToCaptureEncoding . metaQuery . meta)
  (mapRequestBody (Many . pure . bodyCodecToBodyEncoding) . metaRequestBody . meta)
  (mapResponseBody (Many . pure . bodyCodecToBodyDecoding) . metaResponseBody . meta)

shouldRight :: Show resp => Config -> Prepared Route resp -> IO ()
shouldRight conf route = do
  putStr $ show (link route) ++ ": "
  client conf route >>= \case
    Left err -> do
      print err
      exitFailure
    Right val -> print val

main :: IO ()
main = do
  manager <- N.newManager N.defaultManagerSettings
  let conf = Config (Authority Http "httpbin.org" Nothing) manager
  res <- catch (client conf (prepare RouteHome)) $ \(_ :: SomeException) -> return (Left (status N.status400))
  case res of
    Left err  -> do
      putStrLn "Could not connect to httpbin.org, not running test suite"
      putStrLn ("Could not connect because: " ++ show err)
    Right _ -> do
      putStrLn "Connected to httpbin.org, actually testing routes now..."
      shouldRight conf (prepare RouteIp)
      shouldRight conf (prepare RouteStatus 200)
      shouldRight conf (prepare RouteQuery (Just 1))