git.iko.soy/typeable/http-api-data
on master help sign in
Rephrase documentation and amend some examples for FormUrlEncoded
fb08da7Nickolay Kudasov
src/Web/HttpApiData/Internal/FormUrlEncoded.hs+119−95
@@ -34,55 +34,66 @@ import Web.HttpApiData.Internal.HttpApiData
34
34
35-- $setup
35-- $setup
36-- >>> :set -XDeriveGeneric
36-- >>> :set -XDeriveGeneric
37+-- >>> :set -XOverloadedLists
38+-- >>> :set -XOverloadedStrings
39+-- >>> :set -XFlexibleContexts
37-- >>> :set -XScopedTypeVariables
40-- >>> :set -XScopedTypeVariables
38-- >>> :set -XTypeFamilies
41-- >>> :set -XTypeFamilies
39-- >>> import Data.Either (isLeft)
42-- >>> import Data.Either (isLeft)
40-- >>> import Data.List (sort)
43-- >>> import Data.List (sort)
44+-- >>> data Person = Person { name :: String, age :: Int } deriving (Show, Generic)
45+-- >>> instance ToForm Person
46+-- >>> instance FromForm Person
41
47
42−-- | The contents of a form, not yet url-encoded.
48+-- | The contents of a form, not yet URL-encoded.
43--
49--
44−-- Forms can be URL-encoded with 'encodeForm'. Url-encoded bytestrings can be
50+-- 'Form' can be URL-encoded with 'encodeForm' and URL-decoded with 'decodeForm'.
45−-- converted to a 'Form' with 'decodeForm'.
46newtype Form = Form { unForm :: M.Map T.Text T.Text }
51newtype Form = Form { unForm :: M.Map T.Text T.Text }
47 deriving (Eq, Show, Read, Generic, Monoid)
52 deriving (Eq, Show, Read, Generic, Monoid)
48
53
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")]
54instance IsList Form where
54instance IsList Form where
55 type Item Form = (T.Text, T.Text)
55 type Item Form = (T.Text, T.Text)
56 fromList = Form . M.fromList
56 fromList = Form . M.fromList
57 toList = M.toList . unForm
57 toList = M.toList . unForm
58
58
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 }
60--
69--
61−-- >>> let form = toForm [("foo" :: T.Text, "bar" :: T.Text)]
70+-- instance 'ToForm' Person where
62−-- >>> encodeForm form
71+-- 'toForm' person =
63−-- "foo=bar"
72+-- [ (\"name\", 'toQueryParam' (name person))
73+-- , (\"age\", 'toQueryParam' (age person)) ]
74+-- @
64--
75--
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'@.
66--
78--
67−-- >>> data MyFoo = MyFoo { myFooBar :: T.Text }
79+-- To do that, simply add @deriving 'Generic'@ clause to your datatype
68−-- >>> instance ToForm MyFoo where toForm (MyFoo bar') = fromList [("bar", bar')]
80+-- and declare a 'ToForm' instance for your datatype without
69−-- >>> let form = toForm $ MyFoo { myFooBar = "hello" }
81+-- giving definition for 'toForm'.
70−-- >>> encodeForm form
71−-- "bar=hello"
72--
82--
73−-- There is a default method for 'toForm' for any Haskell record type that
83+-- For instance, the previous example can be simplified into this:
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'.
77--
84--
78−-- This can be used like the following:
85+-- @
86+-- data Person = Person
87+-- { name :: String
88+-- , age :: Int
89+-- } deriving ('Generic')
79--
90--
80−-- >>> data MyGenericBar = MyGenericBar { myGenericBar :: Int } deriving Generic
81−-- >>> instance ToForm MyGenericBar
91+-- instance 'ToForm' Person
82−-- >>> let form = toForm $ MyGenericBar { myGenericBar = 3 }
92+-- @
83−-- >>> encodeForm form
93+--
84−-- "myGenericBar=3"
94+-- The default implementation will use 'toQueryParam' for each field's value.
85class ToForm a where
95class ToForm a where
96+ -- | Convert a value into 'Form'.
86 toForm :: a -> Form
97 toForm :: a -> Form
87 default toForm :: (Generic a, GToForm (Rep a)) => a -> Form
98 default toForm :: (Generic a, GToForm (Rep a)) => a -> Form
88 toForm = genericToForm
99 toForm = genericToForm
@@ -91,6 +102,8 @@ instance ToForm [(T.Text, T.Text)] where toForm = Form . M.fromList
91instance ToForm (M.Map T.Text T.Text) where toForm = Form
102instance ToForm (M.Map T.Text T.Text) where toForm = Form
92instance ToForm Form where toForm = id
103instance ToForm Form where toForm = id
93
104
105+-- | A 'Generic'-based implementation of 'toForm'.
106+-- This is used as a default implementation in 'ToForm'.
94genericToForm :: (Generic a, GToForm (Rep a)) => a -> Form
107genericToForm :: (Generic a, GToForm (Rep a)) => a -> Form
95genericToForm x = evalState (unGenericToFormS . gToForm $ from x) (Nothing, 0)
108genericToForm x = evalState (unGenericToFormS . gToForm $ from x) (Nothing, 0)
96
109
@@ -132,37 +145,42 @@ instance (ToHttpApiData f) => GToForm (K1 i f) where
132 where name (Nothing, i) = T.pack $ show i
145 where name (Nothing, i) = T.pack $ show i
133 name (Just x, _) = x
146 name (Just x, _) = x
134
147
135−-- | A type that can be converted from a 'Form', with the possibility of
148+-- | Parse 'Form' into a value.
136−-- failure.
137−--
149+--
138−-- >>> let form = fromList [("foo" :: T.Text, "bar" :: T.Text)]
150+-- An example type and instance:
139−-- >>> fromForm form :: Either T.Text [(T.Text, T.Text)]
140−-- Right [("foo","bar")]
141−--
151+--
142−-- Your own datatypes can be converted from a 'Form' like the following:
152+-- @
143−--
153+-- data Person = Person
144−-- >>> data MyFoo = MyFoo { myFooBar :: T.Text } deriving Show
154+-- { name :: String
145−-- >>> instance FromForm MyFoo where fromForm (Form map') = maybe (Left "key \"bar\" not found") (Right . MyFoo) $ M.lookup "bar" map'
155+-- , age :: Int }
146−-- >>> let form = fromList [("bar" :: T.Text, "hello" :: T.Text)]
156+--
147−-- >>> fromForm form :: Either T.Text MyFoo
157+-- instance 'FromForm' Person where
148−-- Right (MyFoo {myFooBar = "hello"})
158+-- 'fromForm' (Form m) = Person
149−--
159+-- '\<$\>' maybe (Left "key \"name\" not found") 'parseQueryParam' (lookup "name" m)
150−-- There is a default method for 'fromForm' for any Haskell record type that
160+-- '\<*\>' maybe (Left "key \"age\" not found") 'parseQueryParam' (lookup "name" m)
151−-- derives 'Generic'. The record's fields will be taken from the keys of the
161+-- @
152−-- 'Form'. This only works for fields with types that implement
162+--
153−-- 'FromHttpApiData'.
163+-- Instead of manually writing @'FromForm'@ instances you can
154−--
164+-- use a default generic implementation of @'fromForm'@.
155−-- This can be used like the following:
165+--
156−--
166+-- To do that, simply add @deriving 'Generic'@ clause to your datatype
157−-- >>> data MyGenericBar = MyGenericBar { myGenericBar :: Int } deriving (Generic, Show)
167+-- and declare a 'FromForm' instance for your datatype without
158−-- >>> instance FromForm MyGenericBar
168+-- giving definition for 'fromForm'.
159−-- >>> let form = fromList [("myGenericBar" :: T.Text, "1" :: T.Text)]
169+--
160−-- >>> fromForm form :: Either T.Text MyGenericBar
170+-- For instance, the previous example can be simplified into this:
161−-- Right (MyGenericBar {myGenericBar = 1})
171+--
162−-- >>> let form = fromList [("some other key" :: T.Text, "2" :: T.Text)]
172+-- @
163−-- >>> isLeft (fromForm form :: Either T.Text MyGenericBar)
173+-- data Person = Person
164−-- True
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.
165class FromForm a where
182class FromForm a where
183+ -- | Parse 'Form' into a value.
166 fromForm :: Form -> Either T.Text a
184 fromForm :: Form -> Either T.Text a
167 default fromForm :: (Generic a, GFromForm (Rep a))
185 default fromForm :: (Generic a, GFromForm (Rep a))
168 => Form -> Either T.Text a
186 => Form -> Either T.Text a
@@ -172,6 +190,8 @@ instance FromForm Form where fromForm = return
172instance FromForm [(T.Text, T.Text)] where fromForm = return . M.toList . unForm
190instance FromForm [(T.Text, T.Text)] where fromForm = return . M.toList . unForm
173instance FromForm (M.Map T.Text T.Text) where fromForm = return . unForm
191instance FromForm (M.Map T.Text T.Text) where fromForm = return . unForm
174
192
193+-- | A 'Generic'-based implementation of 'fromForm'.
194+-- This is used as a default implementation in 'FromForm'.
175genericFromForm :: (Generic a, GFromForm (Rep a))
195genericFromForm :: (Generic a, GFromForm (Rep a))
176 => Form -> Either T.Text a
196 => Form -> Either T.Text a
177genericFromForm f = to <$> gFromForm f
197genericFromForm f = to <$> gFromForm f
@@ -204,33 +224,33 @@ instance (GFromForm f) => GFromForm (M1 D x f) where
204instance (GFromForm f) => GFromForm (M1 C x f) where
224instance (GFromForm f) => GFromForm (M1 C x f) where
205 gFromForm f = M1 <$> gFromForm f
225 gFromForm f = M1 <$> gFromForm f
206
226
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'.
208--
228--
209−-- Key-value pairs get encoded to @key=value@.
229+-- Key-value pairs get encoded to @key=value@ and separated by @&@:
210--
230--
211−-- >>> encodeForm $ toForm [("foo" :: T.Text, "bar" :: T.Text)]
231+-- >>> encodeForm [("name", "Julian"), ("lastname", "Arni")]
212−-- "foo=bar"
232+-- "lastname=Arni&name=Julian"
213--
233--
214−-- Keys with no values get encoded to just @key@.
234+-- Keys with empty values get encoded to just @key@ (without the @=@ sign):
215--
235--
216−-- >>> encodeForm $ toForm [("foo" :: T.Text, "" :: T.Text)]
236+-- >>> encodeForm [("is_test", "")]
217−-- "foo"
237+-- "is_test"
218--
238--
219−-- Empty keys don't show up.
239+-- Empty keys are allowed too:
220--
240--
221−-- >>> encodeForm $ toForm [("" :: T.Text, "bar" :: T.Text)]
241+-- >>> encodeForm [("", "foobar")]
222−-- "=bar"
242+-- "=foobar"
223--
243--
224−-- When both they key and value is empty, it gets encoded as nothing. (This
244+-- However, if not key and value are empty, the key-value pair is ignored.
225−-- prevents @'decodeForm' . 'encodeForm'@ from being an isomorphism).
245+-- (This prevents @'decodeForm' . 'encodeForm'@ from being a true isomorphism).
226--
246--
227−-- >>> encodeForm $ toForm [("" :: T.Text, "" :: T.Text)]
247+-- >>> encodeForm [("", "")]
228-- ""
248-- ""
229--
249--
230−-- Other characters are escaped by @'escapeURIString' 'isUnreserved'@.
250+-- Everything is escaped with @'escapeURIString' 'isUnreserved'@:
231--
251--
232−-- >>> encodeForm $ toForm [("foo" :: T.Text, "value with spaces" :: T.Text)]
252+-- >>> encodeForm [("fullname", "Andres Löh")]
233−-- "foo=value%20with%20spaces"
253+-- "fullname=Andres%20L%C3%B6h"
234encodeForm :: Form -> BSL.ByteString
254encodeForm :: Form -> BSL.ByteString
235encodeForm xs =
255encodeForm xs =
236 let escape :: T.Text -> BSL.ByteString
256 let escape :: T.Text -> BSL.ByteString
@@ -240,39 +260,37 @@ encodeForm xs =
240 encodePair (k, v) = escape k <> "=" <> escape v
260 encodePair (k, v) = escape k <> "=" <> escape v
241 in BSL.intercalate "&" $ map encodePair $ toList xs
261 in BSL.intercalate "&" $ map encodePair $ toList xs
242
262
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'.
244--
264--
245−-- Key-value pairs get decoded normally.
265+-- Key-value pairs get decoded normally:
246--
266--
247−-- >>> toList <$> decodeForm "foo=bar"
267+-- >>> toList <$> decodeForm "name=Greg&lastname=Weber"
248−-- Right [("foo","bar")]
268+-- Right [("lastname","Weber"),("name","Greg")]
249−-- >>> sort . toList <$> decodeForm "foo=bar&abc=xyz"
250−-- Right [("abc","xyz"),("foo","bar")]
251--
269--
252−-- Keys with no values get decoded to empty values.
270+-- Keys with no values get decoded to pairs with empty values.
253--
271--
254−-- >>> toList <$> decodeForm "foo"
272+-- >>> toList <$> decodeForm "is_test"
255−-- Right [("foo","")]
273+-- Right [("is_test","")]
256--
274--
257−-- Empty keys get decoded as @""@.
275+-- Empty keys are allowed:
258--
276--
259−-- >>> toList <$> decodeForm "=bar"
277+-- >>> toList <$> decodeForm "=foobar"
260−-- Right [("","bar")]
278+-- Right [("","foobar")]
261--
279--
262−-- The empty string gets decoded as no key-value pairs.
280+-- The empty string gets decoded into an empty 'Form':
263--
281--
264-- >>> toList <$> decodeForm ""
282-- >>> toList <$> decodeForm ""
265-- Right []
283-- Right []
266--
284--
267−-- Other characters are un-escaped with 'unEscapeString'.
285+-- Everything is un-escaped with 'unEscapeString':
268--
286--
269−-- >>> toList <$> decodeForm "foo=value%20with%20spaces"
287+-- >>> toList <$> decodeForm "fullname=Andres%20L%C3%B6h"
270−-- Right [("foo","value with spaces")]
288+-- Right [("fullname","Andres L\246h")]
271--
289--
272−-- Improperly formed strings return 'Left'.
290+-- Improperly formed strings result in an error:
273--
291--
274−-- >>> isLeft $ decodeForm "this=has=too=many=equals"
292+-- >>> decodeForm "this=has=too=many=equals"
275−-- True
293+-- Left "not a valid pair: this=has=too=many=equals"
276decodeForm :: BSL.ByteString -> Either T.Text Form
294decodeForm :: BSL.ByteString -> Either T.Text Form
277decodeForm "" = return mempty
295decodeForm "" = return mempty
278decodeForm q = do
296decodeForm q = do
@@ -297,7 +315,10 @@ data Proxy3 a b c = Proxy3
297-- that has an instance for 'FromForm'.
315-- that has an instance for 'FromForm'.
298--
316--
299-- This is effectively @'fromForm' '<=<' 'decodeForm'@.
317-- This is effectively @'fromForm' '<=<' 'decodeForm'@.
318+--
319+-- >>> decodeWithFromForm "name=Dennis&age=22" :: Either T.Text Person
320+-- Right (Person {name = "Dennis", age = 22})
300−decodeWithFromForm :: (FromForm a) => BSL.ByteString -> Either T.Text a
321+decodeWithFromForm :: FromForm a => BSL.ByteString -> Either T.Text a
301decodeWithFromForm = fromForm <=< decodeForm
322decodeWithFromForm = fromForm <=< decodeForm
302
323
303-- | This is a convenience function for encoding a datatype that has instance
324-- | This is a convenience function for encoding a datatype that has instance
@@ -305,5 +326,8 @@ decodeWithFromForm = fromForm <=< decodeForm
305-- 'BSL.ByteString'.
326-- 'BSL.ByteString'.
306--
327--
307-- This is effectively @'encodeForm' . 'toForm'@.
328-- This is effectively @'encodeForm' . 'toForm'@.
329+--
330+-- >>> encodeWithToForm Person {name = "Dennis", age = 22}
331+-- "age=22&name=Dennis"
308−encodeWithToForm :: (ToForm a) => a -> BSL.ByteString
332+encodeWithToForm :: ToForm a => a -> BSL.ByteString
309encodeWithToForm = encodeForm . toForm
333encodeWithToForm = encodeForm . toForm
src/Web/HttpApiData/Internal/HttpApiData.hs+5−4
@@ -44,6 +44,11 @@ import Text.ParserCombinators.ReadP (readP_to_S)
44import TextShow (TextShow, showt)
44import TextShow (TextShow, showt)
45#endif
45#endif
46
46
47+-- $setup
48+-- >>> :unset -XOverloadedLists
49+-- >>> data BasicAuthToken = BasicAuthToken Text deriving (Show)
50+-- >>> instance FromHttpApiData BasicAuthToken where parseHeader h = BasicAuthToken <$> parseHeaderWithPrefix "Basic " h; parseQueryParam p = BasicAuthToken <$> parseQueryParam p
51+
47-- | Convert value to HTTP API data.
52-- | Convert value to HTTP API data.
48--
53--
49-- __WARNING__: Do not derive this using @DeriveAnyClass@ as the generated
54-- __WARNING__: Do not derive this using @DeriveAnyClass@ as the generated
@@ -212,10 +217,6 @@ parseUrlPieceWithPrefix pattern input
212 where
217 where
213 (prefix, rest) = T.splitAt (T.length pattern) input
218 (prefix, rest) = T.splitAt (T.length pattern) input
214
219
215−-- $setup
216−-- >>> data BasicAuthToken = BasicAuthToken Text deriving (Show)
217−-- >>> instance FromHttpApiData BasicAuthToken where parseHeader h = BasicAuthToken <$> parseHeaderWithPrefix "Basic " h; parseQueryParam p = BasicAuthToken <$> parseQueryParam p
218−
219-- | Parse given bytestring then parse the rest of the input using @'parseHeader'@.
220-- | Parse given bytestring then parse the rest of the input using @'parseHeader'@.
220--
221--
221-- @
222-- @