{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Control.Applicative ((<|>))
import Data.Maybe (fromMaybe)
import Data.Proxy (Proxy (..))
import Servant
import Servant.Pagination
import qualified Data.Char as Char
import qualified Network.Wai.Handler.Warp as Warp
import Color
-- Ranges definitions
-- By default, a Range relies on `defaultOptions` but any instance can define its own options
instance HasPagination Color "name" where
type RangeType Color "name" = String
getFieldValue _ = name
getRangeOptions _ _ = defaultOptions
{ defaultRangeLimit = 5
, defaultRangeOrder = RangeAsc
}
-- We can declare more than one range on a given type if they use different symbol field
instance HasPagination Color "hex" where
type RangeType Color "hex" = String
getFieldValue _ = map Char.toUpper . hex
instance HasPagination Color "rgb" where
type RangeType Color "rgb" = Int
getFieldValue _ = sum . rgb
-- API
type API =
"colors"
:> Header "Range" (Ranges '["name", "rgb", "hex"] Color)
:> GetPartialContent '[JSON] (Headers MyHeaders [Color])
-- PageHeaders fields resource ~ '[Header h typ], thus we can add extra headers
-- as we desire.
type MyHeaders =
Header "Total-Count" Int ': PageHeaders '["name", "rgb", "hex"] Color
-- Application
defaultRange :: Range "name" String
defaultRange =
getDefaultRange (Proxy @Color)
server :: Server API
server mrange =
addHeader (length colors) <$> handler mrange
where
-- 'extractRange' tries to extract a range if it has the right type, and yields 'Nothing'
-- otherwise. We can use the '<|>' alternative combinator to try handlers one after
-- the other
handler r =
fromMaybe (returnNameRange defaultRange) $
fmap returnNameRange (r >>= extractRange)
<|> fmap returnRGBRange (r >>= extractRange)
<|> fmap returnHexRange (r >>= extractRange)
-- Handlers below are very simple, in practice, they're likely to trigger different functions
-- or database calls.
returnNameRange (range :: Range "name" String) =
returnRange range (applyRange range colors)
returnRGBRange (range :: Range "rgb" Int) =
returnRange range (applyRange range colors)
returnHexRange (range :: Range "hex" String) =
returnRange range (applyRange range colors)
main :: IO ()
main =
Warp.run 1337 (serve (Proxy @API) server)
-- Examples
-- $ curl -v http://localhost:1337/colors
--
-- > GET /colors HTTP/1.1
--
-- < HTTP/1.1 206 Partial Content
-- < Content-Type: application/json;charset=utf-8
-- < Accept-Ranges: name,rgb
-- < Content-Range: name Aqua..CadetBlue
-- < Next-Range: name CadetBlue;limit 5;offset 1;order asc
-- < Total-Count: 59
-- $ curl -v http://localhost:1337/colors --header 'Range: rgb'
--
-- > GET /colors HTTP/1.1
-- > Range: rgb
--
-- < HTTP/1.1 206 Partial Content
-- < Content-Type: application/json;charset=utf-8
-- < Accept-Ranges: name,rgb
-- < Content-Range: rgb 765..0
-- < Next-Range: rgb 0;limit 100;offset 1;order desc
-- < Total-Count: 59
-- $ curl -v http://localhost:1337/colors --header 'Range: name Green; limit 10; order desc'
--
-- > Get /colors HTTP/1.1
-- > Range: name; limit 10; order desc
-- >
-- < HTTP/1.1 206 Partial Content
-- < Content-Type: application/json;charset=utf-8
-- < Accept-Ranges: name,rgb
-- < Content-Range: name Green..DarkMagenta
-- < Next-Range: name DarkMagenta;limit 10;offset 1;order desc
-- < Total-Count: 59