git.iko.soy/typeable/http-api-data
on 264331d help sign in
typeable/http-api-data/Web/HttpApiData.hs
164 lines 5.7 kB history raw
1{-# LANGUAGE DefaultSignatures #-}
2{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE TypeSynonymInstances #-}
5-- |
6-- Convert Haskell values to and from HTTP API data
7-- such as URL pieces, headers and query parameters.
8module Web.HttpApiData (
9 -- * Examples
10 -- $examples
12 -- * Classes
13 ToHttpApiData (..),
14 FromHttpApiData (..),
15 -- * Utility functions
16 parseMaybeHttpApiData,
17 showUrlPiece,
18 readMaybeUrlPiece,
19 readEitherUrlPiece,
20) where
22import Control.Arrow ((+++))
24import Data.Monoid
25import Data.ByteString (ByteString)
27import Data.Int
28import Data.Word
30import Data.Text (Text)
31import Data.Text.Encoding (encodeUtf8, decodeUtf8)
32import Data.Text.Read (signed, decimal, rational, Reader)
33import qualified Data.Text as Text
34import qualified Data.Text.Lazy as TL
36import Data.Time (Day)
38import Text.Read (readMaybe)
40-- $setup
41--
42-- >>> :set -XOverloadedStrings
43-- >>> import Data.Time
45-- | Convert value to HTTP API data.
46class ToHttpApiData a where
47 -- | Convert to URL path piece.
48 toUrlPiece :: a -> Text
49 toUrlPiece = toQueryParam
51 -- | Convert to HTTP header value.
52 toHeader :: a -> ByteString
53 toHeader = encodeUtf8 . toUrlPiece
55 -- | Convert to query param value.
56 toQueryParam :: a -> Text
57 toQueryParam = toUrlPiece
59-- | Convert value from HTTP API data.
60class FromHttpApiData a where
61 -- | Parse URL path piece.
62 parseUrlPiece :: Text -> Either Text a
63 parseUrlPiece = parseQueryParam
65 -- | Parse HTTP header value.
66 parseHeader :: ByteString -> Either Text a
67 parseHeader = parseUrlPiece . decodeUtf8
69 -- | Parse query param value.
70 parseQueryParam :: Text -> Either Text a
71 parseQueryParam = parseUrlPiece
73-- | Convert @'Maybe'@ parser into @'Either' 'Text'@ parser with default error message.
74parseMaybeHttpApiData :: (Text -> Maybe a) -> (Text -> Either Text a)
75parseMaybeHttpApiData parse input =
76 case parse input of
77 Nothing -> Left ("could not convert: `" <> input <> "'")
78 Just val -> Right val
80-- | Convert to URL piece using @'Show'@ instance.
81showUrlPiece :: Show a => a -> Text
82showUrlPiece = Text.pack . show
84-- | Parse URL piece using @'Read'@ instance.
85readMaybeUrlPiece :: Read a => Text -> Maybe a
86readMaybeUrlPiece = readMaybe . Text.unpack
88-- | Parse URL piece using @'Read'@ instance.
89readEitherUrlPiece :: Read a => Text -> Either Text a
90readEitherUrlPiece = parseMaybeHttpApiData readMaybeUrlPiece
92-- | Run @'Reader'@ as HTTP API data parser.
93runReader :: Reader a -> Text -> Either Text a
94runReader reader = (Text.pack +++ fst) . reader
96instance ToHttpApiData Bool where toUrlPiece = showUrlPiece
97instance ToHttpApiData Double where toUrlPiece = showUrlPiece
98instance ToHttpApiData Float where toUrlPiece = showUrlPiece
99instance ToHttpApiData Int where toUrlPiece = showUrlPiece
100instance ToHttpApiData Int8 where toUrlPiece = showUrlPiece
101instance ToHttpApiData Int16 where toUrlPiece = showUrlPiece
102instance ToHttpApiData Int32 where toUrlPiece = showUrlPiece
103instance ToHttpApiData Int64 where toUrlPiece = showUrlPiece
104instance ToHttpApiData Integer where toUrlPiece = showUrlPiece
105instance ToHttpApiData Word where toUrlPiece = showUrlPiece
106instance ToHttpApiData Word8 where toUrlPiece = showUrlPiece
107instance ToHttpApiData Word16 where toUrlPiece = showUrlPiece
108instance ToHttpApiData Word32 where toUrlPiece = showUrlPiece
109instance ToHttpApiData Word64 where toUrlPiece = showUrlPiece
110instance ToHttpApiData String where toUrlPiece = Text.pack
111instance ToHttpApiData Text where toUrlPiece = id
112instance ToHttpApiData TL.Text where toUrlPiece = TL.toStrict
113instance ToHttpApiData Day where toUrlPiece = showUrlPiece
115instance FromHttpApiData Bool where parseUrlPiece = readEitherUrlPiece
116instance FromHttpApiData Double where parseUrlPiece = runReader rational
117instance FromHttpApiData Float where parseUrlPiece = runReader rational
118instance FromHttpApiData Int where parseUrlPiece = runReader (signed decimal)
119instance FromHttpApiData Int8 where parseUrlPiece = runReader (signed decimal)
120instance FromHttpApiData Int16 where parseUrlPiece = runReader (signed decimal)
121instance FromHttpApiData Int32 where parseUrlPiece = runReader (signed decimal)
122instance FromHttpApiData Int64 where parseUrlPiece = runReader (signed decimal)
123instance FromHttpApiData Integer where parseUrlPiece = runReader (signed decimal)
124instance FromHttpApiData Word where parseUrlPiece = runReader decimal
125instance FromHttpApiData Word8 where parseUrlPiece = runReader decimal
126instance FromHttpApiData Word16 where parseUrlPiece = runReader decimal
127instance FromHttpApiData Word32 where parseUrlPiece = runReader decimal
128instance FromHttpApiData Word64 where parseUrlPiece = runReader decimal
129instance FromHttpApiData String where parseUrlPiece = Right . Text.unpack
130instance FromHttpApiData Text where parseUrlPiece = Right
131instance FromHttpApiData TL.Text where parseUrlPiece = Right . TL.fromStrict
132instance FromHttpApiData Day where parseUrlPiece = readEitherUrlPiece
134-- $examples
135--
136-- Booleans:
137--
138-- >>> toUrlPiece True
139-- "True"
140-- >>> parseUrlPiece "False" :: Either Text Bool
141-- Right False
142-- >>> parseUrlPiece "something else" :: Either Text Bool
143-- Left "could not convert: `something else'"
144--
145-- Numbers:
146--
147-- >>> toUrlPiece 45.2
148-- "45.2"
149-- >>> parseUrlPiece "452" :: Either Text Int
150-- Right 452
151--
152-- Strings:
153--
154-- >>> toHeader "hello"
155-- "hello"
156-- >>> parseHeader "world" :: Either Text String
157-- Right "world"
158--
159-- Calendar day:
160--
161-- >>> toQueryParam (fromGregorian 2015 10 03)
162-- "2015-10-03"
163-- >>> toGregorian <$> parseQueryParam "2016-12-01"
164-- Right (2016,12,1)