git.iko.soy/typeable/http-api-data
on master help sign in
Merge pull request #16 from fizruk/internal-modules

Pre-release touches

commit54ab6f27960e386c6bb272c893687eecd4e9518d authorNickolay Kudasov <fizruk@users.noreply.github.com> dateWed 7 Oct 2015, 16:48 (11 years ago) parentded768bc9807e2
9 files changed+442−559
CHANGELOG.md+1 ChangeLog.md−7 README.md+1−1 Web/HttpApiData.hs+6−274 Web/HttpApiData/Internal.hs+322 Web/PathPieces.hs−161 http-api-data.cabal+6−6 test/Spec.hs+106 test/main.hs−110
CHANGELOG.mdnew file+1
@@ -0,0 +1 @@
1+
ChangeLog.mddeleted−7
@@ -1,7 +0,0 @@
1−## 0.2.0
2−
3−* Make `PathMultiPiece` instance for lists based on `PathPiece` [Yesod issue #932](https://github.com/yesodweb/yesod/issues/932)
4−
5−## 0.1.5
6−
7−Added more integral instances, and check bounds on them [#5](https://github.com/yesodweb/path-pieces/pull/5).
README.md+1−1
@@ -14,7 +14,7 @@ Booleans:
1414>>> parseUrlPiece "False" :: Either Text Bool
1515Right False
1616>>> parseUrlPiece "something else" :: Either Text Bool
17−Left "could not convert: `something else'"
17+Left "could not parse: `something else'"
1818```
1919
2020Numbers:
Web/HttpApiData.hs+6−274
@@ -1,9 +1,3 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DefaultSignatures #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE TypeSynonymInstances #-}
71-- |
82-- Convert Haskell values to and from HTTP API data
93-- such as URL pieces, headers and query parameters.
@@ -14,281 +8,19 @@ module Web.HttpApiData (
148 -- * Classes
159 ToHttpApiData (..),
1610 FromHttpApiData (..),
17−
18− -- * Utility functions
19− parseMaybeTextData,
20− showTextData,
21− readMaybeTextData,
22− readEitherTextData,
2311) where
2412
25−import Control.Applicative
26−import Control.Arrow ((&&&))
27−
28−import Data.Monoid
29−import Data.ByteString (ByteString)
30−
31−import Data.Int
32−import Data.Word
33−
34−import Data.Text (Text)
35−import Data.Text.Encoding (encodeUtf8, decodeUtf8)
36−import Data.Text.Read (signed, decimal, rational, Reader)
37−import qualified Data.Text as T
38−import qualified Data.Text.Lazy as L
39−
40−import Data.Time (Day)
41−import Data.Version
42−
43−#if MIN_VERSION_base(4,8,0)
44−import Data.Void
45−#endif
46−
47−import Text.Read (readMaybe)
48−import Text.ParserCombinators.ReadP (readP_to_S)
13+import Web.HttpApiData.Internal
4914
5015-- $setup
5116--
5217-- >>> :set -XOverloadedStrings
5318-- >>> import Control.Applicative
5419-- >>> import Data.Time
55−
56−-- | Convert value to HTTP API data.
57−class ToHttpApiData a where
58− {-# MINIMAL toUrlPiece | toQueryParam #-}
59− -- | Convert to URL path piece.
60− toUrlPiece :: a -> Text
61− toUrlPiece = toQueryParam
62−
63− -- | Convert to HTTP header value.
64− toHeader :: a -> ByteString
65− toHeader = encodeUtf8 . toUrlPiece
66−
67− -- | Convert to query param value.
68− toQueryParam :: a -> Text
69− toQueryParam = toUrlPiece
70−
71−-- | Convert value from HTTP API data.
72−class FromHttpApiData a where
73− {-# MINIMAL parseUrlPiece | parseQueryParam #-}
74− -- | Parse URL path piece.
75− parseUrlPiece :: Text -> Either Text a
76− parseUrlPiece = parseQueryParam
77−
78− -- | Parse HTTP header value.
79− parseHeader :: ByteString -> Either Text a
80− parseHeader = parseUrlPiece . decodeUtf8
81−
82− -- | Parse query param value.
83− parseQueryParam :: Text -> Either Text a
84− parseQueryParam = parseUrlPiece
85−
86−-- | Default parsing error.
87−defaultParseError :: Text -> Either Text a
88−defaultParseError input = Left ("could not convert: `" <> input <> "'")
89−
90−-- | Convert @'Maybe'@ parser into @'Either' 'Text'@ parser with default error message.
91−parseMaybeTextData :: (Text -> Maybe a) -> (Text -> Either Text a)
92−parseMaybeTextData parse input =
93− case parse input of
94− Nothing -> defaultParseError input
95− Just val -> Right val
96−
97−-- | Convert to URL piece using @'Show'@ instance.
98−showTextData :: Show a => a -> Text
99−showTextData = T.toLower . T.pack . show
100−
101−-- | Parse given text case insensitive and return the rest of the input.
102−--
103−-- >>> parseUrlPieceWithPrefix "Just " "just 10" :: Either Text Int
104−-- Right 10
105−-- >>> parseUrlPieceWithPrefix "Left " "left" :: Either Text Bool
106−-- Left "could not convert: `left'"
107−parseUrlPieceWithPrefix :: FromHttpApiData a => Text -> Text -> Either Text a
108−parseUrlPieceWithPrefix pattern input
109− | T.toLower pattern == T.toLower prefix = parseUrlPiece rest
110− | otherwise = defaultParseError input
111− where
112− (prefix, rest) = T.splitAt (T.length pattern) input
113−
114−-- | Parse values case insensitively based on @'Show'@ instance.
115−--
116−-- >>> parseBoundedCaseInsensitiveTextData "true" :: Either Text Bool
117−-- Right True
118−-- >>> parseBoundedCaseInsensitiveTextData "FALSE" :: Either Text Bool
119−-- Right False
120−parseBoundedCaseInsensitiveTextData :: forall a. (Show a, Bounded a, Enum a) => Text -> Either Text a
121−parseBoundedCaseInsensitiveTextData = parseMaybeTextData (flip lookup values . T.toLower)
122− where
123− values = map (showTextData &&& id) [minBound..maxBound :: a]
124−
125−-- | Parse URL piece using @'Read'@ instance.
126−readMaybeTextData :: Read a => Text -> Maybe a
127−readMaybeTextData = readMaybe . T.unpack
128−
129−-- | Parse URL piece using @'Read'@ instance.
130−readEitherTextData :: Read a => Text -> Either Text a
131−readEitherTextData = parseMaybeTextData readMaybeTextData
132−
133−-- | Run @'Reader'@ as HTTP API data parser.
134−runReader :: Reader a -> Text -> Either Text a
135−runReader reader input =
136− case reader input of
137− Left err -> Left (T.pack err)
138− Right (x, rest)
139− | T.null rest -> Right x
140− | otherwise -> defaultParseError input
141−
142−-- | Run @'Reader'@ to parse bounded integral value with bounds checking.
143−parseBounded :: forall a. (Bounded a, Integral a) => Reader Integer -> Text -> Either Text a
144−parseBounded reader input = do
145− n <- runReader reader input
146− if (n > h || n < l)
147− then Left ("out of bounds: `" <> input <> "' (should be between " <> T.pack (show l) <> " and " <> T.pack (show h) <> ")")
148− else Right (fromInteger n)
149− where
150− l = toInteger (minBound :: a)
151− h = toInteger (maxBound :: a)
152−
153−-- |
154−-- >>> toUrlPiece ()
155−-- "_"
156−instance ToHttpApiData () where
157− toUrlPiece () = "_"
158−
159−instance ToHttpApiData Char where toUrlPiece = T.singleton
160−
161−-- |
162−-- >>> toUrlPiece (Version [1, 2, 3] [])
163−-- "1.2.3"
164−instance ToHttpApiData Version where
165− toUrlPiece = T.pack . showVersion
166−
167−#if MIN_VERSION_base(4,8,0)
168−instance ToHttpApiData Void where
169− toUrlPiece = absurd
170−#endif
171−
172−instance ToHttpApiData Bool where toUrlPiece = showTextData
173−instance ToHttpApiData Ordering where toUrlPiece = showTextData
174−instance ToHttpApiData Double where toUrlPiece = showTextData
175−instance ToHttpApiData Float where toUrlPiece = showTextData
176−instance ToHttpApiData Int where toUrlPiece = showTextData
177−instance ToHttpApiData Int8 where toUrlPiece = showTextData
178−instance ToHttpApiData Int16 where toUrlPiece = showTextData
179−instance ToHttpApiData Int32 where toUrlPiece = showTextData
180−instance ToHttpApiData Int64 where toUrlPiece = showTextData
181−instance ToHttpApiData Integer where toUrlPiece = showTextData
182−instance ToHttpApiData Word where toUrlPiece = showTextData
183−instance ToHttpApiData Word8 where toUrlPiece = showTextData
184−instance ToHttpApiData Word16 where toUrlPiece = showTextData
185−instance ToHttpApiData Word32 where toUrlPiece = showTextData
186−instance ToHttpApiData Word64 where toUrlPiece = showTextData
187−instance ToHttpApiData String where toUrlPiece = T.pack
188−instance ToHttpApiData Text where toUrlPiece = id
189−instance ToHttpApiData L.Text where toUrlPiece = L.toStrict
190−instance ToHttpApiData Day where toUrlPiece = showTextData
191−
192−instance ToHttpApiData All where toUrlPiece = toUrlPiece . getAll
193−instance ToHttpApiData Any where toUrlPiece = toUrlPiece . getAny
194−
195−instance ToHttpApiData a => ToHttpApiData (Dual a) where toUrlPiece = toUrlPiece . getDual
196−instance ToHttpApiData a => ToHttpApiData (Sum a) where toUrlPiece = toUrlPiece . getSum
197−instance ToHttpApiData a => ToHttpApiData (Product a) where toUrlPiece = toUrlPiece . getProduct
198−instance ToHttpApiData a => ToHttpApiData (First a) where toUrlPiece = toUrlPiece . getFirst
199−instance ToHttpApiData a => ToHttpApiData (Last a) where toUrlPiece = toUrlPiece . getLast
200−
201−-- |
202−-- >>> toUrlPiece (Just "Hello")
203−-- "just Hello"
204−instance ToHttpApiData a => ToHttpApiData (Maybe a) where
205− toUrlPiece (Just x) = "just " <> toUrlPiece x
206− toUrlPiece Nothing = "nothing"
207−
208−-- |
209−-- >>> toUrlPiece (Left "err" :: Either String Int)
210−-- "left err"
211−-- >>> toUrlPiece (Right 3 :: Either String Int)
212−-- "right 3"
213−instance (ToHttpApiData a, ToHttpApiData b) => ToHttpApiData (Either a b) where
214− toUrlPiece (Left x) = "left " <> toUrlPiece x
215− toUrlPiece (Right x) = "right " <> toUrlPiece x
216−
217−-- |
218−-- >>> parseUrlPiece "_" :: Either Text ()
219−-- Right ()
220−instance FromHttpApiData () where
221− parseUrlPiece "_" = return ()
222− parseUrlPiece s = defaultParseError s
223−
224−instance FromHttpApiData Char where
225− parseUrlPiece s =
226− case T.uncons s of
227− Just (c, s') | T.null s' -> return c
228− _ -> defaultParseError s
229−
230−-- |
231−-- >>> showVersion <$> parseUrlPiece "1.2.3"
232−-- Right "1.2.3"
233−instance FromHttpApiData Version where
234− parseUrlPiece s =
235− case reverse (readP_to_S parseVersion (T.unpack s)) of
236− ((x, ""):_) -> return x
237− _ -> defaultParseError s
238−
239−#if MIN_VERSION_base(4,8,0)
240−instance FromHttpApiData Void where
241− parseUrlPiece _ = Left "Void cannot be parsed!"
242−#endif
243−
244−instance FromHttpApiData Bool where parseUrlPiece = parseBoundedCaseInsensitiveTextData
245−instance FromHttpApiData Ordering where parseUrlPiece = parseBoundedCaseInsensitiveTextData
246−instance FromHttpApiData Double where parseUrlPiece = runReader rational
247−instance FromHttpApiData Float where parseUrlPiece = runReader rational
248−instance FromHttpApiData Int where parseUrlPiece = parseBounded (signed decimal)
249−instance FromHttpApiData Int8 where parseUrlPiece = parseBounded (signed decimal)
250−instance FromHttpApiData Int16 where parseUrlPiece = parseBounded (signed decimal)
251−instance FromHttpApiData Int32 where parseUrlPiece = parseBounded (signed decimal)
252−instance FromHttpApiData Int64 where parseUrlPiece = parseBounded (signed decimal)
253−instance FromHttpApiData Integer where parseUrlPiece = runReader (signed decimal)
254−instance FromHttpApiData Word where parseUrlPiece = parseBounded decimal
255−instance FromHttpApiData Word8 where parseUrlPiece = parseBounded decimal
256−instance FromHttpApiData Word16 where parseUrlPiece = parseBounded decimal
257−instance FromHttpApiData Word32 where parseUrlPiece = parseBounded decimal
258−instance FromHttpApiData Word64 where parseUrlPiece = parseBounded decimal
259−instance FromHttpApiData String where parseUrlPiece = Right . T.unpack
260−instance FromHttpApiData Text where parseUrlPiece = Right
261−instance FromHttpApiData L.Text where parseUrlPiece = Right . L.fromStrict
262−instance FromHttpApiData Day where parseUrlPiece = readEitherTextData
263−
264−instance FromHttpApiData All where parseUrlPiece = fmap All . parseUrlPiece
265−instance FromHttpApiData Any where parseUrlPiece = fmap Any . parseUrlPiece
266−
267−instance FromHttpApiData a => FromHttpApiData (Dual a) where parseUrlPiece = fmap Dual . parseUrlPiece
268−instance FromHttpApiData a => FromHttpApiData (Sum a) where parseUrlPiece = fmap Sum . parseUrlPiece
269−instance FromHttpApiData a => FromHttpApiData (Product a) where parseUrlPiece = fmap Product . parseUrlPiece
270−instance FromHttpApiData a => FromHttpApiData (First a) where parseUrlPiece = fmap First . parseUrlPiece
271−instance FromHttpApiData a => FromHttpApiData (Last a) where parseUrlPiece = fmap Last . parseUrlPiece
272−
273−-- |
274−-- >>> parseUrlPiece "Just 123" :: Either Text (Maybe Int)
275−-- Right (Just 123)
276−instance FromHttpApiData a => FromHttpApiData (Maybe a) where
277− parseUrlPiece s
278− | T.toLower (T.take 7 s) == "nothing" = return Nothing
279− | otherwise = Just <$> parseUrlPieceWithPrefix "Just " s
280−
281−-- |
282−-- >>> parseUrlPiece "Right 123" :: Either Text (Either String Int)
283−-- Right (Right 123)
284−instance (FromHttpApiData a, FromHttpApiData b) => FromHttpApiData (Either a b) where
285− parseUrlPiece s =
286− Right <$> parseUrlPieceWithPrefix "Right " s
287− <!> Left <$> parseUrlPieceWithPrefix "Left " s
288− where
289− infixl 3 <!>
290− Left _ <!> y = y
291− x <!> _ = x
20+-- >>> import Data.Int
21+-- >>> import Data.Text (Text)
22+-- >>> import Data.Time (Day)
23+-- >>> import Data.Version
29224
29325-- $examples
29426--
@@ -299,7 +31,7 @@ instance (FromHttpApiData a, FromHttpApiData b) => FromHttpApiData (Either a b)
29931-- >>> parseUrlPiece "false" :: Either Text Bool
30032-- Right False
30133-- >>> parseUrlPiece "something else" :: Either Text Bool
302−-- Left "could not convert: `something else'"
34+-- Left "could not parse: `something else'"
30335--
30436-- Numbers:
30537--
Web/HttpApiData/Internal.hsnew file+322
@@ -0,0 +1,322 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DefaultSignatures #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE TypeSynonymInstances #-}
7+-- |
8+-- Convert Haskell values to and from HTTP API data
9+-- such as URL pieces, headers and query parameters.
10+module Web.HttpApiData.Internal where
11+
12+import Control.Applicative
13+import Control.Arrow ((&&&))
14+
15+import Data.Monoid
16+import Data.ByteString (ByteString)
17+
18+import Data.Int
19+import Data.Word
20+
21+import Data.Text (Text)
22+import Data.Text.Encoding (encodeUtf8, decodeUtf8)
23+import Data.Text.Read (signed, decimal, rational, Reader)
24+import qualified Data.Text as T
25+import qualified Data.Text.Lazy as L
26+
27+import Data.Time (Day)
28+import Data.Version
29+
30+#if MIN_VERSION_base(4,8,0)
31+import Data.Void
32+#endif
33+
34+import Text.Read (readMaybe)
35+import Text.ParserCombinators.ReadP (readP_to_S)
36+
37+-- | Convert value to HTTP API data.
38+class ToHttpApiData a where
39+ {-# MINIMAL toUrlPiece | toQueryParam #-}
40+ -- | Convert to URL path piece.
41+ toUrlPiece :: a -> Text
42+ toUrlPiece = toQueryParam
43+
44+ -- | Convert to HTTP header value.
45+ toHeader :: a -> ByteString
46+ toHeader = encodeUtf8 . toUrlPiece
47+
48+ -- | Convert to query param value.
49+ toQueryParam :: a -> Text
50+ toQueryParam = toUrlPiece
51+
52+-- | Parse value from HTTP API data.
53+class FromHttpApiData a where
54+ {-# MINIMAL parseUrlPiece | parseQueryParam #-}
55+ -- | Parse URL path piece.
56+ parseUrlPiece :: Text -> Either Text a
57+ parseUrlPiece = parseQueryParam
58+
59+ -- | Parse HTTP header value.
60+ parseHeader :: ByteString -> Either Text a
61+ parseHeader = parseUrlPiece . decodeUtf8
62+
63+ -- | Parse query param value.
64+ parseQueryParam :: Text -> Either Text a
65+ parseQueryParam = parseUrlPiece
66+
67+-- | Parse URL path piece in a @'Maybe'@.
68+--
69+-- >>> parseUrlPieceMaybe "12" :: Maybe Int
70+-- Just 12
71+parseUrlPieceMaybe :: FromHttpApiData a => Text -> Maybe a
72+parseUrlPieceMaybe = either (const Nothing) Just . parseUrlPiece
73+
74+-- | Parse HTTP header value in a @'Maybe'@.
75+--
76+-- >>> parseHeaderMaybe "hello" :: Maybe Text
77+-- Just "hello"
78+parseHeaderMaybe :: FromHttpApiData a => ByteString -> Maybe a
79+parseHeaderMaybe = either (const Nothing) Just . parseHeader
80+
81+-- | Parse query param value in a @'Maybe'@.
82+--
83+-- >>> parseQueryParamMaybe "true" :: Maybe Bool
84+-- Just True
85+parseQueryParamMaybe :: FromHttpApiData a => Text -> Maybe a
86+parseQueryParamMaybe = either (const Nothing) Just . parseQueryParam
87+
88+-- | Default parsing error.
89+defaultParseError :: Text -> Either Text a
90+defaultParseError input = Left ("could not parse: `" <> input <> "'")
91+
92+-- | Convert @'Maybe'@ parser into @'Either' 'Text'@ parser with default error message.
93+parseMaybeTextData :: (Text -> Maybe a) -> (Text -> Either Text a)
94+parseMaybeTextData parse input =
95+ case parse input of
96+ Nothing -> defaultParseError input
97+ Just val -> Right val
98+
99+-- | Convert to URL piece using @'Show'@ instance.
100+-- The result is always lower cased.
101+--
102+-- >>> showTextData True
103+-- "true"
104+--
105+-- This can be used as a default implementation for enumeration types:
106+--
107+-- >>> data MyData = Foo | Bar | Baz deriving (Show)
108+-- >>> instance ToHttpApiData MyData where toUrlPiece = showTextData
109+-- >>> toUrlPiece Foo
110+-- "foo"
111+showTextData :: Show a => a -> Text
112+showTextData = T.toLower . T.pack . show
113+
114+-- | Parse given text case insensitive and return the rest of the input.
115+--
116+-- >>> parseUrlPieceWithPrefix "Just " "just 10" :: Either Text Int
117+-- Right 10
118+-- >>> parseUrlPieceWithPrefix "Left " "left" :: Either Text Bool
119+-- Left "could not parse: `left'"
120+--
121+-- This can be used to implement @'FromHttpApiData'@ for single field constructors:
122+--
123+-- >>> data Foo = Foo Int deriving (Show)
124+-- >>> instance FromHttpApiData Foo where parseUrlPiece s = Foo <$> parseUrlPieceWithPrefix "Foo " s
125+-- >>> parseUrlPiece "foo 1" :: Either Text Foo
126+-- Right (Foo 1)
127+parseUrlPieceWithPrefix :: FromHttpApiData a => Text -> Text -> Either Text a
128+parseUrlPieceWithPrefix pattern input
129+ | T.toLower pattern == T.toLower prefix = parseUrlPiece rest
130+ | otherwise = defaultParseError input
131+ where
132+ (prefix, rest) = T.splitAt (T.length pattern) input
133+
134+-- | Parse values case insensitively based on @'Show'@ instance.
135+--
136+-- >>> parseBoundedCaseInsensitiveTextData "true" :: Either Text Bool
137+-- Right True
138+-- >>> parseBoundedCaseInsensitiveTextData "FALSE" :: Either Text Bool
139+-- Right False
140+--
141+-- This can be used as a default implementation for enumeration types:
142+--
143+-- >>> data MyData = Foo | Bar | Baz deriving (Show, Bounded, Enum)
144+-- >>> instance FromHttpApiData MyData where parseUrlPiece = parseBoundedCaseInsensitiveTextData
145+-- >>> parseUrlPiece "foo" :: Either Text MyData
146+-- Right Foo
147+parseBoundedCaseInsensitiveTextData :: forall a. (Show a, Bounded a, Enum a) => Text -> Either Text a
148+parseBoundedCaseInsensitiveTextData = parseMaybeTextData (flip lookup values . T.toLower)
149+ where
150+ values = map (showTextData &&& id) [minBound..maxBound :: a]
151+
152+-- | Parse URL piece using @'Read'@ instance.
153+readMaybeTextData :: Read a => Text -> Maybe a
154+readMaybeTextData = readMaybe . T.unpack
155+
156+-- | Parse URL piece using @'Read'@ instance.
157+readEitherTextData :: Read a => Text -> Either Text a
158+readEitherTextData = parseMaybeTextData readMaybeTextData
159+
160+-- | Run @'Reader'@ as HTTP API data parser.
161+runReader :: Reader a -> Text -> Either Text a
162+runReader reader input =
163+ case reader input of
164+ Left err -> Left (T.pack err)
165+ Right (x, rest)
166+ | T.null rest -> Right x
167+ | otherwise -> defaultParseError input
168+
169+-- | Run @'Reader'@ to parse bounded integral value with bounds checking.
170+--
171+-- >>> parseBounded decimal "256" :: Either Text Word8
172+-- Left "out of bounds: `256' (should be between 0 and 255)"
173+parseBounded :: forall a. (Bounded a, Integral a) => Reader Integer -> Text -> Either Text a
174+parseBounded reader input = do
175+ n <- runReader reader input
176+ if (n > h || n < l)
177+ then Left ("out of bounds: `" <> input <> "' (should be between " <> T.pack (show l) <> " and " <> T.pack (show h) <> ")")
178+ else Right (fromInteger n)
179+ where
180+ l = toInteger (minBound :: a)
181+ h = toInteger (maxBound :: a)
182+
183+-- |
184+-- >>> toUrlPiece ()
185+-- "_"
186+instance ToHttpApiData () where
187+ toUrlPiece () = "_"
188+
189+instance ToHttpApiData Char where toUrlPiece = T.singleton
190+
191+-- |
192+-- >>> toUrlPiece (Version [1, 2, 3] [])
193+-- "1.2.3"
194+instance ToHttpApiData Version where
195+ toUrlPiece = T.pack . showVersion
196+
197+#if MIN_VERSION_base(4,8,0)
198+instance ToHttpApiData Void where
199+ toUrlPiece = absurd
200+#endif
201+
202+instance ToHttpApiData Bool where toUrlPiece = showTextData
203+instance ToHttpApiData Ordering where toUrlPiece = showTextData
204+instance ToHttpApiData Double where toUrlPiece = showTextData
205+instance ToHttpApiData Float where toUrlPiece = showTextData
206+instance ToHttpApiData Int where toUrlPiece = showTextData
207+instance ToHttpApiData Int8 where toUrlPiece = showTextData
208+instance ToHttpApiData Int16 where toUrlPiece = showTextData
209+instance ToHttpApiData Int32 where toUrlPiece = showTextData
210+instance ToHttpApiData Int64 where toUrlPiece = showTextData
211+instance ToHttpApiData Integer where toUrlPiece = showTextData
212+instance ToHttpApiData Word where toUrlPiece = showTextData
213+instance ToHttpApiData Word8 where toUrlPiece = showTextData
214+instance ToHttpApiData Word16 where toUrlPiece = showTextData
215+instance ToHttpApiData Word32 where toUrlPiece = showTextData
216+instance ToHttpApiData Word64 where toUrlPiece = showTextData
217+instance ToHttpApiData String where toUrlPiece = T.pack
218+instance ToHttpApiData Text where toUrlPiece = id
219+instance ToHttpApiData L.Text where toUrlPiece = L.toStrict
220+instance ToHttpApiData Day where toUrlPiece = showTextData
221+
222+instance ToHttpApiData All where toUrlPiece = toUrlPiece . getAll
223+instance ToHttpApiData Any where toUrlPiece = toUrlPiece . getAny
224+
225+instance ToHttpApiData a => ToHttpApiData (Dual a) where toUrlPiece = toUrlPiece . getDual
226+instance ToHttpApiData a => ToHttpApiData (Sum a) where toUrlPiece = toUrlPiece . getSum
227+instance ToHttpApiData a => ToHttpApiData (Product a) where toUrlPiece = toUrlPiece . getProduct
228+instance ToHttpApiData a => ToHttpApiData (First a) where toUrlPiece = toUrlPiece . getFirst
229+instance ToHttpApiData a => ToHttpApiData (Last a) where toUrlPiece = toUrlPiece . getLast
230+
231+-- |
232+-- >>> toUrlPiece (Just "Hello")
233+-- "just Hello"
234+instance ToHttpApiData a => ToHttpApiData (Maybe a) where
235+ toUrlPiece (Just x) = "just " <> toUrlPiece x
236+ toUrlPiece Nothing = "nothing"
237+
238+-- |
239+-- >>> toUrlPiece (Left "err" :: Either String Int)
240+-- "left err"
241+-- >>> toUrlPiece (Right 3 :: Either String Int)
242+-- "right 3"
243+instance (ToHttpApiData a, ToHttpApiData b) => ToHttpApiData (Either a b) where
244+ toUrlPiece (Left x) = "left " <> toUrlPiece x
245+ toUrlPiece (Right x) = "right " <> toUrlPiece x
246+
247+-- |
248+-- >>> parseUrlPiece "_" :: Either Text ()
249+-- Right ()
250+instance FromHttpApiData () where
251+ parseUrlPiece "_" = return ()
252+ parseUrlPiece s = defaultParseError s
253+
254+instance FromHttpApiData Char where
255+ parseUrlPiece s =
256+ case T.uncons s of
257+ Just (c, s') | T.null s' -> return c
258+ _ -> defaultParseError s
259+
260+-- |
261+-- >>> showVersion <$> parseUrlPiece "1.2.3"
262+-- Right "1.2.3"
263+instance FromHttpApiData Version where
264+ parseUrlPiece s =
265+ case reverse (readP_to_S parseVersion (T.unpack s)) of
266+ ((x, ""):_) -> return x
267+ _ -> defaultParseError s
268+
269+#if MIN_VERSION_base(4,8,0)
270+instance FromHttpApiData Void where
271+ parseUrlPiece _ = Left "Void cannot be parsed!"
272+#endif
273+
274+instance FromHttpApiData Bool where parseUrlPiece = parseBoundedCaseInsensitiveTextData
275+instance FromHttpApiData Ordering where parseUrlPiece = parseBoundedCaseInsensitiveTextData
276+instance FromHttpApiData Double where parseUrlPiece = runReader rational
277+instance FromHttpApiData Float where parseUrlPiece = runReader rational
278+instance FromHttpApiData Int where parseUrlPiece = parseBounded (signed decimal)
279+instance FromHttpApiData Int8 where parseUrlPiece = parseBounded (signed decimal)
280+instance FromHttpApiData Int16 where parseUrlPiece = parseBounded (signed decimal)
281+instance FromHttpApiData Int32 where parseUrlPiece = parseBounded (signed decimal)
282+instance FromHttpApiData Int64 where parseUrlPiece = parseBounded (signed decimal)
283+instance FromHttpApiData Integer where parseUrlPiece = runReader (signed decimal)
284+instance FromHttpApiData Word where parseUrlPiece = parseBounded decimal
285+instance FromHttpApiData Word8 where parseUrlPiece = parseBounded decimal
286+instance FromHttpApiData Word16 where parseUrlPiece = parseBounded decimal
287+instance FromHttpApiData Word32 where parseUrlPiece = parseBounded decimal
288+instance FromHttpApiData Word64 where parseUrlPiece = parseBounded decimal
289+instance FromHttpApiData String where parseUrlPiece = Right . T.unpack
290+instance FromHttpApiData Text where parseUrlPiece = Right
291+instance FromHttpApiData L.Text where parseUrlPiece = Right . L.fromStrict
292+instance FromHttpApiData Day where parseUrlPiece = readEitherTextData
293+
294+instance FromHttpApiData All where parseUrlPiece = fmap All . parseUrlPiece
295+instance FromHttpApiData Any where parseUrlPiece = fmap Any . parseUrlPiece
296+
297+instance FromHttpApiData a => FromHttpApiData (Dual a) where parseUrlPiece = fmap Dual . parseUrlPiece
298+instance FromHttpApiData a => FromHttpApiData (Sum a) where parseUrlPiece = fmap Sum . parseUrlPiece
299+instance FromHttpApiData a => FromHttpApiData (Product a) where parseUrlPiece = fmap Product . parseUrlPiece
300+instance FromHttpApiData a => FromHttpApiData (First a) where parseUrlPiece = fmap First . parseUrlPiece
301+instance FromHttpApiData a => FromHttpApiData (Last a) where parseUrlPiece = fmap Last . parseUrlPiece
302+
303+-- |
304+-- >>> parseUrlPiece "Just 123" :: Either Text (Maybe Int)
305+-- Right (Just 123)
306+instance FromHttpApiData a => FromHttpApiData (Maybe a) where
307+ parseUrlPiece s
308+ | T.toLower (T.take 7 s) == "nothing" = return Nothing
309+ | otherwise = Just <$> parseUrlPieceWithPrefix "Just " s
310+
311+-- |
312+-- >>> parseUrlPiece "Right 123" :: Either Text (Either String Int)
313+-- Right (Right 123)
314+instance (FromHttpApiData a, FromHttpApiData b) => FromHttpApiData (Either a b) where
315+ parseUrlPiece s =
316+ Right <$> parseUrlPieceWithPrefix "Right " s
317+ <!> Left <$> parseUrlPieceWithPrefix "Left " s
318+ where
319+ infixl 3 <!>
320+ Left _ <!> y = y
321+ x <!> _ = x
322+
Web/PathPieces.hsdeleted−161
@@ -1,161 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DefaultSignatures #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−{-# LANGUAGE Rank2Types #-}
6−{-# LANGUAGE ScopedTypeVariables #-}
7−{-# LANGUAGE TypeSynonymInstances #-}
8−-- |
9−-- Convert Haskell values to and from route pieces.
10−module Web.PathPieces (
11− PathPiece (..),
12− PathMultiPiece (..),
13− readFromPathPiece,
14− showToPathPiece,
15− WrappedPathPiece(..),
16−) where
17−
18−import Data.Monoid
19−import Data.Int
20−import Data.Word
21−import qualified Data.Text as S
22−import qualified Data.Text.Lazy as L
23−import Data.Version (Version)
24−import Data.Time (Day)
25−import Text.Read (readMaybe)
26−
27−import Unsafe.Coerce
28−
29−#if MIN_VERSION_base(4,8,0)
30−import Data.Void (Void)
31−#endif
32−
33−import Web.HttpApiData
34−
35−-- | Convert Haskell values to and from route piece.
36−class PathPiece s where
37− -- | Convert from route piece.
38− fromPathPiece :: S.Text -> Maybe s
39− default fromPathPiece :: FromHttpApiData s => S.Text -> Maybe s
40− fromPathPiece = either (const Nothing) Just . parseUrlPiece
41−
42− -- | Convert to route piece.
43− toPathPiece :: s -> S.Text
44− default toPathPiece :: ToHttpApiData s => s -> S.Text
45− toPathPiece = toUrlPiece
46−
47−instance PathPiece ()
48−instance PathPiece Char
49−instance PathPiece Bool
50−instance PathPiece Ordering
51−instance PathPiece Double
52−instance PathPiece Float
53−instance PathPiece Int
54−instance PathPiece Int8
55−instance PathPiece Int16
56−instance PathPiece Int32
57−instance PathPiece Int64
58−instance PathPiece Integer
59−instance PathPiece Word
60−instance PathPiece Word8
61−instance PathPiece Word16
62−instance PathPiece Word32
63−instance PathPiece Word64
64−instance PathPiece String
65−instance PathPiece S.Text
66−instance PathPiece L.Text
67−instance PathPiece Day
68−instance PathPiece Version
69−
70−#if MIN_VERSION_base(4,8,0)
71−instance PathPiece Void
72−#endif
73−
74−instance PathPiece All
75−instance PathPiece Any
76−
77−instance PathPiece a => PathPiece (Dual a) where
78− toPathPiece = toPathPiece1
79− fromPathPiece = fromPathPiece1
80−
81−instance PathPiece a => PathPiece (Sum a) where
82− toPathPiece = toPathPiece1
83− fromPathPiece = fromPathPiece1
84−
85−instance PathPiece a => PathPiece (Product a) where
86− toPathPiece = toPathPiece1
87− fromPathPiece = fromPathPiece1
88−
89−instance PathPiece a => PathPiece (First a) where
90− toPathPiece = toPathPiece1
91− fromPathPiece = fromPathPiece1
92−
93−instance PathPiece a => PathPiece (Last a) where
94− toPathPiece = toPathPiece1
95− fromPathPiece = fromPathPiece1
96−
97−instance PathPiece a => PathPiece (Maybe a) where
98− toPathPiece = toPathPiece1
99− fromPathPiece = fromPathPiece1
100−
101−instance (PathPiece a, PathPiece b) => PathPiece (Either a b) where
102− toPathPiece = toPathPiece2
103− fromPathPiece = fromPathPiece2
104−
105−-- | Wrapped @'PathPiece'@ value.
106−newtype WrappedPathPiece a = WrappedPathPiece { unwrapPathPiece :: a }
107−
108−instance PathPiece a => ToHttpApiData (WrappedPathPiece a) where
109− toUrlPiece = toPathPiece . unwrapPathPiece
110−
111−instance PathPiece a => FromHttpApiData (WrappedPathPiece a) where
112− parseUrlPiece = fmap WrappedPathPiece . parseMaybeTextData fromPathPiece
113−
114−-- | Convert value to route piece using @'PathPiece'@ instance for its parameter.
115−toPathPiece1 :: forall f a. (PathPiece a, ToHttpApiData (f (WrappedPathPiece a))) => f a -> S.Text
116−toPathPiece1 = toUrlPiece . (unsafeCoerce :: f a -> f (WrappedPathPiece a))
117−
118−-- | Parse value from route piece using @'PathPiece'@ instance for its parameter.
119−fromPathPiece1 :: forall f a. (PathPiece a, FromHttpApiData (f (WrappedPathPiece a))) => S.Text -> Maybe (f a)
120−fromPathPiece1 = either (const Nothing) (Just . (unsafeCoerce :: f (WrappedPathPiece a) -> f a)) . parseUrlPiece
121−
122−-- | Convert value to route piece using @'PathPiece'@ instance for its parameters.
123−toPathPiece2 :: forall f a b. (PathPiece a, PathPiece b, ToHttpApiData (f (WrappedPathPiece a) (WrappedPathPiece b))) => f a b -> S.Text
124−toPathPiece2 = toUrlPiece . (unsafeCoerce :: f a b -> f (WrappedPathPiece a) (WrappedPathPiece b))
125−
126−-- | Parse value from route piece using @'PathPiece'@ instance for its parameters.
127−fromPathPiece2 :: forall f a b. (PathPiece a, PathPiece b, FromHttpApiData (f (WrappedPathPiece a) (WrappedPathPiece b))) => S.Text -> Maybe (f a b)
128−fromPathPiece2 = either (const Nothing) (Just . (unsafeCoerce :: f (WrappedPathPiece a) (WrappedPathPiece b) -> f a b)) . parseUrlPiece
129−
130−-- | Convert Haskell values to and from sequence of route pieces.
131−class PathMultiPiece s where
132− -- | Convert from sequence of route pieces.
133− fromPathMultiPiece :: [S.Text] -> Maybe s
134− -- | Convert to sequence of route pieces.
135− toPathMultiPiece :: s -> [S.Text]
136−
137−instance PathPiece a => PathMultiPiece [a] where
138− fromPathMultiPiece = mapM fromPathPiece
139− toPathMultiPiece = map toPathPiece
140−
141−-- | A function for helping generate free 'PathPiece'
142−-- instances for enumeration data types
143−-- that have derived 'Read' and 'Show' instances.
144−-- Intended to be used like this:
145−--
146−-- > data MyData = Foo | Bar | Baz
147−-- > deriving (Read,Show)
148−-- > instance PathPiece MyData where
149−-- > fromPathPiece = readFromPathPiece
150−-- > toPathPiece = showToPathPiece
151−--
152−-- Since 0.2.1.
153−readFromPathPiece :: Read s => S.Text -> Maybe s
154−readFromPathPiece = readMaybe . S.unpack
155−
156−-- | See the documentation for 'readFromPathPiece'.
157−--
158−-- Since 0.2.1.
159−showToPathPiece :: Show s => s -> S.Text
160−showToPathPiece = S.pack . show
161−
http-api-data.cabal+6−6
@@ -1,5 +1,5 @@
11name: http-api-data
2−version: 0.2.0
2+version: 0.1.0
33license: BSD3
44license-file: LICENSE
55author: Michael Snoyman <michael@snoyman.com>
@@ -12,8 +12,8 @@ stability: unstable
1212cabal-version: >= 1.8
1313build-type: Simple
1414extra-source-files:
15− test/main.hs
16− ChangeLog.md
15+ test/*.hs
16+ CHANGELOG.md
1717 README.md
1818
1919library
@@ -22,13 +22,13 @@ library
2222 , bytestring
2323 , time
2424 exposed-modules:
25− Web.PathPieces
2625 Web.HttpApiData
26+ Web.HttpApiData.Internal
2727 ghc-options: -Wall
2828
29−test-suite test
29+test-suite spec
3030 type: exitcode-stdio-1.0
31− main-is: main.hs
31+ main-is: Spec.hs
3232 hs-source-dirs: test
3333 ghc-options: -Wall
3434 build-depends: HUnit
test/Spec.hsnew file+106
@@ -0,0 +1,106 @@
1+{-# Language ScopedTypeVariables #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
3+module Main where
4+
5+import Control.Applicative
6+
7+import Data.Int
8+import Data.Char
9+import Data.Word
10+import Data.Time
11+import qualified Data.Text as T
12+import qualified Data.Text.Lazy as L
13+import Data.Version
14+
15+import Test.Hspec
16+import Test.Hspec.QuickCheck(prop)
17+import Test.QuickCheck
18+
19+import Web.HttpApiData
20+import Web.HttpApiData.Internal
21+
22+instance Arbitrary T.Text where
23+ arbitrary = T.pack <$> arbitrary
24+
25+instance Arbitrary L.Text where
26+ arbitrary = L.pack <$> arbitrary
27+
28+instance Arbitrary Day where
29+ arbitrary = liftA3 fromGregorian (fmap abs arbitrary) arbitrary arbitrary
30+
31+instance Arbitrary Version where
32+ arbitrary = (version . map abs) <$> nonempty
33+ where
34+ version branch = Version branch []
35+ nonempty = liftA2 (:) arbitrary arbitrary
36+
37+main :: IO ()
38+main = hspec spec
39+
40+(<=>) :: Eq a => (a -> b) -> (b -> Either T.Text a) -> a -> Bool
41+(f <=> g) x = g (f x) == Right x
42+
43+data Proxy a = Proxy
44+
45+checkUrlPiece :: forall a. (Eq a, ToHttpApiData a, FromHttpApiData a, Show a, Arbitrary a) => Proxy a -> String -> Spec
46+checkUrlPiece _ name = prop name (toUrlPiece <=> parseUrlPiece :: a -> Bool)
47+
48+data RandomCase a = RandomCase [Bool] a
49+
50+instance ToHttpApiData a => Show (RandomCase a) where
51+ show rc@(RandomCase _ x) = show (toUrlPiece rc) ++ " (original: " ++ show (toUrlPiece x) ++ ")"
52+
53+instance Eq a => Eq (RandomCase a) where
54+ RandomCase _ x == RandomCase _ y = x == y
55+
56+instance Arbitrary a => Arbitrary (RandomCase a) where
57+ arbitrary = liftA2 RandomCase nonempty arbitrary
58+ where
59+ nonempty = liftA2 (:) arbitrary arbitrary
60+
61+instance ToHttpApiData a => ToHttpApiData (RandomCase a) where
62+ toUrlPiece (RandomCase us x) = T.pack (zipWith (\u -> if u then toUpper else toLower) (cycle us) (T.unpack (toUrlPiece x)))
63+
64+instance FromHttpApiData a => FromHttpApiData (RandomCase a) where
65+ parseUrlPiece s = RandomCase [] <$> parseUrlPiece s
66+
67+-- | Check case insensitivity for @parseUrlPiece@.
68+checkUrlPieceI :: forall a. (Eq a, ToHttpApiData a, FromHttpApiData a, Show a, Arbitrary a) => Proxy a -> String -> Spec
69+checkUrlPieceI _ = checkUrlPiece (Proxy :: Proxy (RandomCase a))
70+
71+spec :: Spec
72+spec = do
73+ describe "toUrlPiece <=> parseUrlPiece" $ do
74+ checkUrlPiece (Proxy :: Proxy ()) "()"
75+ checkUrlPiece (Proxy :: Proxy Char) "Char"
76+ checkUrlPieceI (Proxy :: Proxy Bool) "Bool"
77+ checkUrlPieceI (Proxy :: Proxy Ordering) "Ordering"
78+ checkUrlPiece (Proxy :: Proxy Int) "Int"
79+ checkUrlPiece (Proxy :: Proxy Int8) "Int8"
80+ checkUrlPiece (Proxy :: Proxy Int16) "Int16"
81+ checkUrlPiece (Proxy :: Proxy Int32) "Int32"
82+ checkUrlPiece (Proxy :: Proxy Int64) "Int64"
83+ checkUrlPiece (Proxy :: Proxy Integer) "Integer"
84+ checkUrlPiece (Proxy :: Proxy Word) "Word"
85+ checkUrlPiece (Proxy :: Proxy Word8) "Word8"
86+ checkUrlPiece (Proxy :: Proxy Word16) "Word16"
87+ checkUrlPiece (Proxy :: Proxy Word32) "Word32"
88+ checkUrlPiece (Proxy :: Proxy Word64) "Word64"
89+ checkUrlPiece (Proxy :: Proxy String) "String"
90+ checkUrlPiece (Proxy :: Proxy T.Text) "Text.Strict"
91+ checkUrlPiece (Proxy :: Proxy L.Text) "Text.Lazy"
92+ checkUrlPiece (Proxy :: Proxy Day) "Day"
93+ checkUrlPiece (Proxy :: Proxy Version) "Version"
94+
95+ checkUrlPiece (Proxy :: Proxy (Maybe String)) "Maybe String"
96+ checkUrlPieceI (Proxy :: Proxy (Maybe Integer)) "Maybe Integer"
97+ checkUrlPiece (Proxy :: Proxy (Either Integer T.Text)) "Either Integer Text"
98+ checkUrlPieceI (Proxy :: Proxy (Either Version Day)) "Either Version Day"
99+
100+ it "bad integers are rejected" $ do
101+ parseUrlPieceMaybe (T.pack "123hello") `shouldBe` (Nothing :: Maybe Int)
102+
103+ it "bounds checking works" $ do
104+ parseUrlPieceMaybe (T.pack "256") `shouldBe` (Nothing :: Maybe Int8)
105+ parseUrlPieceMaybe (T.pack "-10") `shouldBe` (Nothing :: Maybe Word)
106+
test/main.hsdeleted−110
@@ -1,110 +0,0 @@
1−{-# Language ScopedTypeVariables #-}
2−{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module Main where
4−
5−import Control.Applicative
6−
7−import Data.Int
8−import Data.Char
9−import Data.Word
10−import Data.Time
11−import qualified Data.Text as T
12−import qualified Data.Text.Lazy as L
13−import Data.Version
14−
15−import Test.Hspec
16−import Test.Hspec.QuickCheck(prop)
17−import Test.QuickCheck
18−
19−import Web.PathPieces
20−
21−instance Arbitrary T.Text where
22− arbitrary = T.pack <$> arbitrary
23−
24−instance Arbitrary L.Text where
25− arbitrary = L.pack <$> arbitrary
26−
27−instance Arbitrary Day where
28− arbitrary = liftA3 fromGregorian (fmap abs arbitrary) arbitrary arbitrary
29−
30−instance Arbitrary Version where
31− arbitrary = (version . map abs) <$> nonempty
32− where
33− version branch = Version branch []
34− nonempty = liftA2 (:) arbitrary arbitrary
35−
36−main :: IO ()
37−main = hspec spec
38−
39−(<=>) :: Eq a => (a -> b) -> (b -> Maybe a) -> a -> Bool
40−(f <=> g) x = g (f x) == Just x
41−
42−data Proxy a = Proxy
43−
44−checkPathPiece :: forall a. (Eq a, PathPiece a, Show a, Arbitrary a) => Proxy a -> String -> Spec
45−checkPathPiece _ name = prop ("toPathPiece <=> fromPathPiece " ++ name) (toPathPiece <=> fromPathPiece :: a -> Bool)
46−
47−checkPathMultiPiece :: forall a. (Eq a, PathMultiPiece a, Show a, Arbitrary a) => Proxy a -> String -> Spec
48−checkPathMultiPiece _ name = prop ("toPathMultiPiece <=> fromPathMultiPiece " ++ name) (toPathMultiPiece <=> fromPathMultiPiece :: a -> Bool)
49−
50−data RandomCase a = RandomCase [Bool] a
51−
52−instance PathPiece a => Show (RandomCase a) where
53− show rc@(RandomCase _ x) = show (toPathPiece rc) ++ " (original: " ++ show (toPathPiece x) ++ ")"
54−
55−instance Eq a => Eq (RandomCase a) where
56− RandomCase _ x == RandomCase _ y = x == y
57−
58−instance Arbitrary a => Arbitrary (RandomCase a) where
59− arbitrary = liftA2 RandomCase nonempty arbitrary
60− where
61− nonempty = liftA2 (:) arbitrary arbitrary
62−
63−instance PathPiece a => PathPiece (RandomCase a) where
64− toPathPiece (RandomCase us x) = T.pack (zipWith (\u -> if u then toUpper else toLower) (cycle us) (T.unpack (toPathPiece x)))
65− fromPathPiece s = RandomCase [] <$> fromPathPiece s
66−
67−-- | Check case insensitivity for @fromPathPiece@.
68−checkPathPieceI :: forall a. (Eq a, PathPiece a, Show a, Arbitrary a) => Proxy a -> String -> Spec
69−checkPathPieceI _ = checkPathPiece (Proxy :: Proxy (RandomCase a))
70−
71−spec :: Spec
72−spec = do
73− describe "PathPiece" $ do
74− checkPathPiece (Proxy :: Proxy ()) "()"
75− checkPathPiece (Proxy :: Proxy Char) "Char"
76− checkPathPieceI (Proxy :: Proxy Bool) "Bool"
77− checkPathPieceI (Proxy :: Proxy Ordering) "Ordering"
78− checkPathPiece (Proxy :: Proxy Int) "Int"
79− checkPathPiece (Proxy :: Proxy Int8) "Int8"
80− checkPathPiece (Proxy :: Proxy Int16) "Int16"
81− checkPathPiece (Proxy :: Proxy Int32) "Int32"
82− checkPathPiece (Proxy :: Proxy Int64) "Int64"
83− checkPathPiece (Proxy :: Proxy Integer) "Integer"
84− checkPathPiece (Proxy :: Proxy Word) "Word"
85− checkPathPiece (Proxy :: Proxy Word8) "Word8"
86− checkPathPiece (Proxy :: Proxy Word16) "Word16"
87− checkPathPiece (Proxy :: Proxy Word32) "Word32"
88− checkPathPiece (Proxy :: Proxy Word64) "Word64"
89− checkPathPiece (Proxy :: Proxy String) "String"
90− checkPathPiece (Proxy :: Proxy T.Text) "Text.Strict"
91− checkPathPiece (Proxy :: Proxy L.Text) "Text.Lazy"
92− checkPathPiece (Proxy :: Proxy Day) "Day"
93− checkPathPiece (Proxy :: Proxy Version) "Version"
94−
95− checkPathPiece (Proxy :: Proxy (Maybe String)) "Maybe String"
96− checkPathPieceI (Proxy :: Proxy (Maybe Integer)) "Maybe Integer"
97− checkPathPiece (Proxy :: Proxy (Either Integer T.Text)) "Either Integer Text"
98− checkPathPieceI (Proxy :: Proxy (Either Version Day)) "Either Version Day"
99−
100− describe "PathMultiPiece" $ do
101− checkPathMultiPiece (Proxy :: Proxy [String]) "[String]"
102− checkPathMultiPiece (Proxy :: Proxy [T.Text]) "[Text]"
103−
104− it "bad integers are rejected" $ do
105− fromPathPiece (T.pack "123hello") `shouldBe` (Nothing :: Maybe Int)
106−
107− it "bounds checking works" $ do
108− fromPathPiece (T.pack "256") `shouldBe` (Nothing :: Maybe Int8)
109− fromPathPiece (T.pack "-10") `shouldBe` (Nothing :: Maybe Word)
110−