@@ -34,55 +34,66 @@ import Web.HttpApiData.Internal.HttpApiData
3434
3535-- $setup
3636-- >>> :set -XDeriveGeneric
37+-- >>> :set -XOverloadedLists
38+-- >>> :set -XOverloadedStrings
39+-- >>> :set -XFlexibleContexts
3740-- >>> :set -XScopedTypeVariables
3841-- >>> :set -XTypeFamilies
3942-- >>> import Data.Either (isLeft)
4043-- >>> import Data.List (sort)
44+-- >>> data Person = Person { name :: String, age :: Int } deriving (Show, Generic)
45+-- >>> instance ToForm Person
46+-- >>> instance FromForm Person
4147
42−-- | The contents of a form, not yet url-encoded.
48+-- | The contents of a form, not yet URL-encoded.
4349--
44−-- Forms can be URL-encoded with 'encodeForm'. Url-encoded bytestrings can be
45−-- converted to a 'Form' with 'decodeForm'.
50+-- 'Form' can be URL-encoded with 'encodeForm' and URL-decoded with 'decodeForm'.
4651newtype Form = Form { unForm :: M.Map T.Text T.Text }
4752 deriving (Eq, Show, Read, Generic, Monoid)
4853
49−-- | Convert a list of @('T.Text', 'T.Text')@ to a 'Form'.
50−--
51−-- >>> let (form :: Form) = fromList [("baz", "qux"), ("foo", "bar")]
52−-- >>> toList form
53−-- [("baz","qux"),("foo","bar")]
5454instance IsList Form where
5555 type Item Form = (T.Text, T.Text)
5656 fromList = Form . M.fromList
5757 toList = M.toList . unForm
5858
59−-- | A type that can be converted to a 'Form'.
59+-- | Convert a value into 'Form'.
60+--
61+-- An example type and instance:
62+--
63+-- @
64+-- {-\# LANGUAGE OverloadedLists \#-}
65+--
66+-- data Person = Person
67+-- { name :: String
68+-- , age :: Int }
6069--
61−-- >>> let form = toForm [("foo" :: T.Text, "bar" :: T.Text)]
62−-- >>> encodeForm form
63−-- "foo=bar"
70+-- instance 'ToForm' Person where
71+-- 'toForm' person =
72+-- [ (\"name\", 'toQueryParam' (name person))
73+-- , (\"age\", 'toQueryParam' (age person)) ]
74+-- @
6475--
65−-- Your own datatypes can be converted to a 'Form' like the following:
76+-- Instead of manually writing @'ToForm'@ instances you can
77+-- use a default generic implementation of @'toForm'@.
6678--
67−-- >>> data MyFoo = MyFoo { myFooBar :: T.Text }
68−-- >>> instance ToForm MyFoo where toForm (MyFoo bar') = fromList [("bar", bar')]
69−-- >>> let form = toForm $ MyFoo { myFooBar = "hello" }
70−-- >>> encodeForm form
71−-- "bar=hello"
79+-- To do that, simply add @deriving 'Generic'@ clause to your datatype
80+-- and declare a 'ToForm' instance for your datatype without
81+-- giving definition for 'toForm'.
7282--
73−-- There is a default method for 'toForm' for any Haskell record type that
74−-- derives 'Generic'. The record's fields will be turned into the keys of the
75−-- 'Form'. This only works for fields with types that implement
76−-- 'ToHttpApiData'.
83+-- For instance, the previous example can be simplified into this:
7784--
78−-- This can be used like the following:
85+-- @
86+-- data Person = Person
87+-- { name :: String
88+-- , age :: Int
89+-- } deriving ('Generic')
7990--
80−-- >>> data MyGenericBar = MyGenericBar { myGenericBar :: Int } deriving Generic
81−-- >>> instance ToForm MyGenericBar
82−-- >>> let form = toForm $ MyGenericBar { myGenericBar = 3 }
83−-- >>> encodeForm form
84−-- "myGenericBar=3"
91+-- instance 'ToForm' Person
92+-- @
93+--
94+-- The default implementation will use 'toQueryParam' for each field's value.
8595class ToForm a where
96+ -- | Convert a value into 'Form'.
8697 toForm :: a -> Form
8798 default toForm :: (Generic a, GToForm (Rep a)) => a -> Form
8899 toForm = genericToForm
@@ -91,6 +102,8 @@ instance ToForm [(T.Text, T.Text)] where toForm = Form . M.fromList
91102instance ToForm (M.Map T.Text T.Text) where toForm = Form
92103instance ToForm Form where toForm = id
93104
105+-- | A 'Generic'-based implementation of 'toForm'.
106+-- This is used as a default implementation in 'ToForm'.
94107genericToForm :: (Generic a, GToForm (Rep a)) => a -> Form
95108genericToForm x = evalState (unGenericToFormS . gToForm $ from x) (Nothing, 0)
96109
@@ -132,37 +145,42 @@ instance (ToHttpApiData f) => GToForm (K1 i f) where
132145 where name (Nothing, i) = T.pack $ show i
133146 name (Just x, _) = x
134147
135−-- | A type that can be converted from a 'Form', with the possibility of
136−-- failure.
137−--
138−-- >>> let form = fromList [("foo" :: T.Text, "bar" :: T.Text)]
139−-- >>> fromForm form :: Either T.Text [(T.Text, T.Text)]
140−-- Right [("foo","bar")]
141−--
142−-- Your own datatypes can be converted from a 'Form' like the following:
143−--
144−-- >>> data MyFoo = MyFoo { myFooBar :: T.Text } deriving Show
145−-- >>> instance FromForm MyFoo where fromForm (Form map') = maybe (Left "key \"bar\" not found") (Right . MyFoo) $ M.lookup "bar" map'
146−-- >>> let form = fromList [("bar" :: T.Text, "hello" :: T.Text)]
147−-- >>> fromForm form :: Either T.Text MyFoo
148−-- Right (MyFoo {myFooBar = "hello"})
149−--
150−-- There is a default method for 'fromForm' for any Haskell record type that
151−-- derives 'Generic'. The record's fields will be taken from the keys of the
152−-- 'Form'. This only works for fields with types that implement
153−-- 'FromHttpApiData'.
154−--
155−-- This can be used like the following:
156−--
157−-- >>> data MyGenericBar = MyGenericBar { myGenericBar :: Int } deriving (Generic, Show)
158−-- >>> instance FromForm MyGenericBar
159−-- >>> let form = fromList [("myGenericBar" :: T.Text, "1" :: T.Text)]
160−-- >>> fromForm form :: Either T.Text MyGenericBar
161−-- Right (MyGenericBar {myGenericBar = 1})
162−-- >>> let form = fromList [("some other key" :: T.Text, "2" :: T.Text)]
163−-- >>> isLeft (fromForm form :: Either T.Text MyGenericBar)
164−-- True
148+-- | Parse 'Form' into a value.
149+--
150+-- An example type and instance:
151+--
152+-- @
153+-- data Person = Person
154+-- { name :: String
155+-- , age :: Int }
156+--
157+-- instance 'FromForm' Person where
158+-- 'fromForm' (Form m) = Person
159+-- '\<$\>' maybe (Left "key \"name\" not found") 'parseQueryParam' (lookup "name" m)
160+-- '\<*\>' maybe (Left "key \"age\" not found") 'parseQueryParam' (lookup "name" m)
161+-- @
162+--
163+-- Instead of manually writing @'FromForm'@ instances you can
164+-- use a default generic implementation of @'fromForm'@.
165+--
166+-- To do that, simply add @deriving 'Generic'@ clause to your datatype
167+-- and declare a 'FromForm' instance for your datatype without
168+-- giving definition for 'fromForm'.
169+--
170+-- For instance, the previous example can be simplified into this:
171+--
172+-- @
173+-- data Person = Person
174+-- { name :: String
175+-- , age :: Int
176+-- } deriving ('Generic')
177+--
178+-- instance 'FromForm' Person
179+-- @
180+--
181+-- The default implementation will use 'parseQueryParam' for each field's value.
165182class FromForm a where
183+ -- | Parse 'Form' into a value.
166184 fromForm :: Form -> Either T.Text a
167185 default fromForm :: (Generic a, GFromForm (Rep a))
168186 => Form -> Either T.Text a
@@ -172,6 +190,8 @@ instance FromForm Form where fromForm = return
172190instance FromForm [(T.Text, T.Text)] where fromForm = return . M.toList . unForm
173191instance FromForm (M.Map T.Text T.Text) where fromForm = return . unForm
174192
193+-- | A 'Generic'-based implementation of 'fromForm'.
194+-- This is used as a default implementation in 'FromForm'.
175195genericFromForm :: (Generic a, GFromForm (Rep a))
176196 => Form -> Either T.Text a
177197genericFromForm f = to <$> gFromForm f
@@ -204,33 +224,33 @@ instance (GFromForm f) => GFromForm (M1 D x f) where
204224instance (GFromForm f) => GFromForm (M1 C x f) where
205225 gFromForm f = M1 <$> gFromForm f
206226
207−-- | Encode a 'Form' to a @application/x-www-form-urlencoded@ 'BSL.ByteString'.
227+-- | Encode a 'Form' to an @application/x-www-form-urlencoded@ 'BSL.ByteString'.
208228--
209−-- Key-value pairs get encoded to @key=value@.
229+-- Key-value pairs get encoded to @key=value@ and separated by @&@:
210230--
211−-- >>> encodeForm $ toForm [("foo" :: T.Text, "bar" :: T.Text)]
212−-- "foo=bar"
231+-- >>> encodeForm [("name", "Julian"), ("lastname", "Arni")]
232+-- "lastname=Arni&name=Julian"
213233--
214−-- Keys with no values get encoded to just @key@.
234+-- Keys with empty values get encoded to just @key@ (without the @=@ sign):
215235--
216−-- >>> encodeForm $ toForm [("foo" :: T.Text, "" :: T.Text)]
217−-- "foo"
236+-- >>> encodeForm [("is_test", "")]
237+-- "is_test"
218238--
219−-- Empty keys don't show up.
239+-- Empty keys are allowed too:
220240--
221−-- >>> encodeForm $ toForm [("" :: T.Text, "bar" :: T.Text)]
222−-- "=bar"
241+-- >>> encodeForm [("", "foobar")]
242+-- "=foobar"
223243--
224−-- When both they key and value is empty, it gets encoded as nothing. (This
225−-- prevents @'decodeForm' . 'encodeForm'@ from being an isomorphism).
244+-- However, if not key and value are empty, the key-value pair is ignored.
245+-- (This prevents @'decodeForm' . 'encodeForm'@ from being a true isomorphism).
226246--
227−-- >>> encodeForm $ toForm [("" :: T.Text, "" :: T.Text)]
247+-- >>> encodeForm [("", "")]
228248-- ""
229249--
230−-- Other characters are escaped by @'escapeURIString' 'isUnreserved'@.
250+-- Everything is escaped with @'escapeURIString' 'isUnreserved'@:
231251--
232−-- >>> encodeForm $ toForm [("foo" :: T.Text, "value with spaces" :: T.Text)]
233−-- "foo=value%20with%20spaces"
252+-- >>> encodeForm [("fullname", "Andres Löh")]
253+-- "fullname=Andres%20L%C3%B6h"
234254encodeForm :: Form -> BSL.ByteString
235255encodeForm xs =
236256 let escape :: T.Text -> BSL.ByteString
@@ -240,39 +260,37 @@ encodeForm xs =
240260 encodePair (k, v) = escape k <> "=" <> escape v
241261 in BSL.intercalate "&" $ map encodePair $ toList xs
242262
243−-- | Decode a @application/x-www-form-urlencoded@ 'BSL.ByteString' to a 'Form'.
263+-- | Decode an @application/x-www-form-urlencoded@ 'BSL.ByteString' to a 'Form'.
244264--
245−-- Key-value pairs get decoded normally.
265+-- Key-value pairs get decoded normally:
246266--
247−-- >>> toList <$> decodeForm "foo=bar"
248−-- Right [("foo","bar")]
249−-- >>> sort . toList <$> decodeForm "foo=bar&abc=xyz"
250−-- Right [("abc","xyz"),("foo","bar")]
267+-- >>> toList <$> decodeForm "name=Greg&lastname=Weber"
268+-- Right [("lastname","Weber"),("name","Greg")]
251269--
252−-- Keys with no values get decoded to empty values.
270+-- Keys with no values get decoded to pairs with empty values.
253271--
254−-- >>> toList <$> decodeForm "foo"
255−-- Right [("foo","")]
272+-- >>> toList <$> decodeForm "is_test"
273+-- Right [("is_test","")]
256274--
257−-- Empty keys get decoded as @""@.
275+-- Empty keys are allowed:
258276--
259−-- >>> toList <$> decodeForm "=bar"
260−-- Right [("","bar")]
277+-- >>> toList <$> decodeForm "=foobar"
278+-- Right [("","foobar")]
261279--
262−-- The empty string gets decoded as no key-value pairs.
280+-- The empty string gets decoded into an empty 'Form':
263281--
264282-- >>> toList <$> decodeForm ""
265283-- Right []
266284--
267−-- Other characters are un-escaped with 'unEscapeString'.
285+-- Everything is un-escaped with 'unEscapeString':
268286--
269−-- >>> toList <$> decodeForm "foo=value%20with%20spaces"
270−-- Right [("foo","value with spaces")]
287+-- >>> toList <$> decodeForm "fullname=Andres%20L%C3%B6h"
288+-- Right [("fullname","Andres L\246h")]
271289--
272−-- Improperly formed strings return 'Left'.
290+-- Improperly formed strings result in an error:
273291--
274−-- >>> isLeft $ decodeForm "this=has=too=many=equals"
275−-- True
292+-- >>> decodeForm "this=has=too=many=equals"
293+-- Left "not a valid pair: this=has=too=many=equals"
276294decodeForm :: BSL.ByteString -> Either T.Text Form
277295decodeForm "" = return mempty
278296decodeForm q = do
@@ -297,7 +315,10 @@ data Proxy3 a b c = Proxy3
297315-- that has an instance for 'FromForm'.
298316--
299317-- This is effectively @'fromForm' '<=<' 'decodeForm'@.
300−decodeWithFromForm :: (FromForm a) => BSL.ByteString -> Either T.Text a
318+--
319+-- >>> decodeWithFromForm "name=Dennis&age=22" :: Either T.Text Person
320+-- Right (Person {name = "Dennis", age = 22})
321+decodeWithFromForm :: FromForm a => BSL.ByteString -> Either T.Text a
301322decodeWithFromForm = fromForm <=< decodeForm
302323
303324-- | This is a convenience function for encoding a datatype that has instance
@@ -305,5 +326,8 @@ decodeWithFromForm = fromForm <=< decodeForm
305326-- 'BSL.ByteString'.
306327--
307328-- This is effectively @'encodeForm' . 'toForm'@.
308−encodeWithToForm :: (ToForm a) => a -> BSL.ByteString
329+--
330+-- >>> encodeWithToForm Person {name = "Dennis", age = 22}
331+-- "age=22&name=Dennis"
332+encodeWithToForm :: ToForm a => a -> BSL.ByteString
309333encodeWithToForm = encodeForm . toForm