Skip to content

Commit

Permalink
Finish
Browse files Browse the repository at this point in the history
  • Loading branch information
kozross committed Jun 1, 2021
1 parent 2d1e755 commit dd9d184
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 204 deletions.
38 changes: 16 additions & 22 deletions shields.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,17 @@ library shields-lib
Shields.Response

build-depends:
, base ==4.14.1.0
, binary ^>=0.8.8.0
, bytestring ^>=0.10.12.1
, io-streams ^>=1.5.2.1
, jsonifier ^>=0.1.1
, ptr-poker ^>=0.1.1.3
, snap-core ^>=1.0.4.2
, base ==4.14.1.0
, binary ^>=0.8.8.0
, bytebuild ^>=0.3.7.0
, io-streams ^>=1.5.2.1
, json-syntax ^>=0.2.0.0
, scientific-notation ^>=0.1.3.0
, snap-core ^>=1.0.4.2
, text-short ^>=0.1.3

hs-source-dirs: src

library snap-client
import: lang
visibility: private
exposed-modules: Snap.Client
build-depends:
, base >=4.12.0.0 && <4.15.0.0
, snap-core

hs-source-dirs: client

-- Executable

executable shields
Expand All @@ -70,17 +61,20 @@ executable shields

-- Tests

test-suite response
test-suite tests
import: lang
type: exitcode-stdio-1.0
main-is: Main.hs
build-depends:
, base
, case-insensitive ^>=1.2.1.0
, byteslice ^>=0.2.5.2
, case-insensitive ^>=1.2.1.0
, json-syntax
, scientific-notation
, shields-lib
, snap-core
, tasty ^>=1.4.1
, tasty-hunit ^>=0.10.0.3
, tasty ^>=1.4.1
, tasty-hunit ^>=0.10.0.3

ghc-options: -O2 -threaded -with-rtsopts=-N
hs-source-dirs: test/response
hs-source-dirs: test
15 changes: 7 additions & 8 deletions src/Shields/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
module Shields.Api (api) where

import Control.Applicative ((<|>))
import Data.Binary.Builder (Builder, fromByteString)
import Jsonifier (toByteString)
import Data.Binary.Builder (Builder, singleton)
import Data.Bytes.Builder (run)
import Data.Bytes.Chunks (foldl')
import Json (encode)
import Shields.Response (defaultResponse, renderResponse)
import Snap.Core
( Method (GET, HEAD),
Expand Down Expand Up @@ -39,10 +41,7 @@ api = heartbeat <|> shield
getResponse >>= finishWith
go :: OutputStream Builder -> IO (OutputStream Builder)
go stream = do
Streams.writeTo stream
. Just
. fromByteString
. toByteString
. renderResponse
$ defaultResponse
let cs = run 4080 . encode . renderResponse $ defaultResponse
let built = foldl' (\b c -> b <> singleton c) mempty cs
Streams.writeTo stream . Just $ built
pure stream
141 changes: 25 additions & 116 deletions src/Shields/Response.hs
Original file line number Diff line number Diff line change
@@ -1,134 +1,43 @@
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE PatternSynonyms #-}

module Shields.Response
( Colour,
renderColour,
Style,
renderStyle,
Logo,
renderLogo,
LogoWidth,
renderLogoWidth,
LogoPosition,
renderLogoPosition,
Seconds,
renderSeconds,
ShieldResponse (..),
( ShieldResponse (..),
renderResponse,
defaultResponse,
)
where

import Data.Coerce (coerce)
import Data.Maybe (catMaybes)
import Jsonifier
( Json,
bool,
intNumber,
object,
textString,
writeJson,
import Data.Number.Scientific (fromWord64)
import Data.Text.Short (ShortText)
import Json
( Value (Number, String),
object3,
pattern (:->),
)
import PtrPoker.Write (Write)

-- | @since 1.0.0
newtype Colour = Colour Write

-- | @since 1.0.0
renderColour :: Colour -> Json
renderColour = writeJson . coerce

-- | @since 1.0.0
newtype Style = Style Write

-- | @since 1.0.0
renderStyle :: Style -> Json
renderStyle = writeJson . coerce

-- | @since 1.0.0
newtype Logo = Logo Write

-- | @since 1.0.0
renderLogo :: Logo -> Json
renderLogo = writeJson . coerce

-- | @since 1.0.0
newtype LogoWidth = LogoWidth Write

-- | @since 1.0.0
renderLogoWidth :: LogoWidth -> Json
renderLogoWidth = writeJson . coerce

-- | @since 1.0.0
newtype LogoPosition = LogoPosition Write

-- | @since 1.0.0
renderLogoPosition :: LogoPosition -> Json
renderLogoPosition = writeJson . coerce

-- | @since 1.0.0
newtype Seconds = Seconds Write

-- | @since 1.0.0
renderSeconds :: Seconds -> Json
renderSeconds = writeJson . coerce

-- | @since 1.0.0
data ShieldResponse = ShieldResponse
newtype ShieldResponse = ShieldResponse
{ -- | @since 1.0.0
label :: {-# UNPACK #-} !Write,
-- | @since 1.0.0
colour :: !(Maybe Colour),
-- | @since 1.0.0
labelColour :: !(Maybe Colour),
-- | @since 1.0.0
logoColour :: !(Maybe Colour),
-- | @since 1.0.0
namedLogo :: !(Maybe Logo),
-- | @since 1.0.0
logoSvg :: !(Maybe Logo),
-- | @since 1.0.0
logoWidth :: !(Maybe LogoWidth),
-- | @since 1.0.0
logoPosition :: !(Maybe LogoPosition),
-- | @since 1.0.0
style :: !(Maybe Style),
-- | @since 1.0.0
cacheSeconds :: !(Maybe Seconds)
label :: ShortText
}
deriving
( -- | @since 1.0.0
Eq,
-- | @since 1.0.0
Show
)
via ShortText

-- | @since 1.0.0
defaultResponse :: ShieldResponse
defaultResponse =
ShieldResponse
{ label = mempty,
colour = Nothing,
labelColour = Nothing,
logoColour = Nothing,
namedLogo = Nothing,
logoSvg = Nothing,
logoWidth = Nothing,
logoPosition = Nothing,
style = Nothing,
cacheSeconds = Nothing
}
defaultResponse = ShieldResponse ""

-- | @since 1.0.0
renderResponse :: ShieldResponse -> Json
renderResponse sr =
object . catMaybes $
[ pure ("schemaVersion", intNumber 1),
pure ("label", writeJson . label $ sr),
pure ("message", textString "Haskell Foundation"),
("color",) . renderColour <$> colour sr,
("labelColor",) . renderColour <$> labelColour sr,
pure ("isError", bool False),
("namedLogo",) . renderLogo <$> namedLogo sr,
("logoSvg",) . renderLogo <$> logoSvg sr,
("logoColor",) . renderColour <$> logoColour sr,
("logoWidth",) . renderLogoWidth <$> logoWidth sr,
("logoPosition",) . renderLogoPosition <$> logoPosition sr,
("style",) . renderStyle <$> style sr,
("cacheSeconds",) . renderSeconds <$> cacheSeconds sr
]
renderResponse :: ShieldResponse -> Value
renderResponse (ShieldResponse l) =
object3
("schemaVersion" :-> (Number . fromWord64 $ 1))
("label" :-> String l)
("message" :-> String "Haskell Foundation")
115 changes: 115 additions & 0 deletions test/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Data.Bytes (fromByteString)
import Data.CaseInsensitive (mk)
import Data.Foldable (find)
import Data.Number.Scientific (fromWord64)
import Json
( Member (Member),
Value (Number, Object, String),
decode,
value,
)
import Shields.Api (api)
import Snap.Core
( Method (HEAD),
getHeader,
rspStatus,
rspStatusReason,
)
import Snap.Test
( RequestBuilder,
RequestType (RequestWithRawBody),
assertSuccess,
get,
getResponseBody,
runHandler,
setRequestPath,
setRequestType,
)
import Test.Tasty (TestTree, defaultMain, testGroup)
import Test.Tasty.HUnit (assertEqual, testCase)
import Prelude hiding (lookup)

main :: IO ()
main = defaultMain tests

tests :: TestTree
tests =
testGroup
"Responses"
[ testCase "Get, /heartbeat" $ do
resp <- runHandler getHeartbeat api
assertSuccess resp
assertEqual "Status reason is 'Ok'" "Ok" . rspStatusReason $ resp,
testCase "Head, /heartbeat" $ do
resp <- runHandler headHeartbeat api
assertSuccess resp
assertEqual "Status reason is 'Ok'" "Ok" . rspStatusReason $ resp,
testCase "Get, /" $ do
resp <- runHandler getTop api
assertEqual "Status code is 200" 200 . rspStatus $ resp
assertEqual "Content type is JSON" (Just "application/json") . getHeader (mk "Content-Type") $ resp
parsed <- decode . fromByteString <$> getResponseBody resp
assertEqual
"Content is a three-attribute object"
(Right . Just $ 3)
(checkObject <$> parsed)
assertEqual
"schemaVersion attribute has value 1"
(Right . Just . Number . fromWord64 $ 1)
(checkSchemaVersion <$> parsed)
assertEqual
"label attribute has value ''"
(Right . Just . String $ "")
(checkLabel <$> parsed)
assertEqual
"message attribute has value 'Haskell Foundation'"
(Right . Just . String $ "Haskell Foundation")
(checkMessage <$> parsed)
]

-- Helpers

checkObject :: Value -> Maybe Int
checkObject = \case
Object mems -> Just . length $ mems
_ -> Nothing

checkSchemaVersion :: Value -> Maybe Value
checkSchemaVersion = \case
Object mems -> value <$> find go mems
_ -> Nothing
where
go :: Member -> Bool
go (Member k _) = k == "schemaVersion"

checkLabel :: Value -> Maybe Value
checkLabel = \case
Object mems -> value <$> find go mems
_ -> Nothing
where
go :: Member -> Bool
go (Member k _) = k == "label"

checkMessage :: Value -> Maybe Value
checkMessage = \case
Object mems -> value <$> find go mems
_ -> Nothing
where
go :: Member -> Bool
go (Member k _) = k == "message"

getTop :: RequestBuilder IO ()
getTop = get "/" mempty

getHeartbeat :: RequestBuilder IO ()
getHeartbeat = get "/heartbeat" mempty

headHeartbeat :: RequestBuilder IO ()
headHeartbeat = do
setRequestType . RequestWithRawBody HEAD $ ""
setRequestPath "/heartbeat"
Loading

0 comments on commit dd9d184

Please sign in to comment.