git.iko.soy/typeable/servant
on master help sign in
Merge pull request #592 from haskell-servant/use-http-api-forms
a274d8aJulian Arni
servant-client/servant-client.cabal+2−1
@@ -41,7 +41,7 @@ library
41 , base64-bytestring >= 1.0.0.1 && < 1.1
41 , base64-bytestring >= 1.0.0.1 && < 1.1
42 , bytestring >= 0.10 && < 0.11
42 , bytestring >= 0.10 && < 0.11
43 , exceptions >= 0.8 && < 0.9
43 , exceptions >= 0.8 && < 0.9
44− , http-api-data >= 0.1 && < 0.3
44+ , http-api-data >= 0.3 && < 0.4
45 , http-client >= 0.4.18.1 && < 0.6
45 , http-client >= 0.4.18.1 && < 0.6
46 , http-client-tls >= 0.2.2 && < 0.4
46 , http-client-tls >= 0.2.2 && < 0.4
47 , http-media >= 0.6.2 && < 0.7
47 , http-media >= 0.6.2 && < 0.7
@@ -78,6 +78,7 @@ test-suite spec
78 , bytestring
78 , bytestring
79 , deepseq
79 , deepseq
80 , hspec == 2.*
80 , hspec == 2.*
81+ , http-api-data
81 , http-client
82 , http-client
82 , http-media
83 , http-media
83 , http-types
84 , http-types
servant-client/test/Servant/ClientSpec.hs+3−14
@@ -39,7 +39,6 @@ import Data.Char (chr, isPrint)
39import Data.Foldable (forM_)
39import Data.Foldable (forM_)
40import Data.Monoid hiding (getLast)
40import Data.Monoid hiding (getLast)
41import Data.Proxy
41import Data.Proxy
42−import qualified Data.Text as T
43import GHC.Generics (Generic)
42import GHC.Generics (Generic)
44import qualified Network.HTTP.Client as C
43import qualified Network.HTTP.Client as C
45import Network.HTTP.Media
44import Network.HTTP.Media
@@ -52,6 +51,7 @@ import Test.Hspec
52import Test.Hspec.QuickCheck
51import Test.Hspec.QuickCheck
53import Test.HUnit
52import Test.HUnit
54import Test.QuickCheck
53import Test.QuickCheck
54+import Web.FormUrlEncoded (FromForm, ToForm)
55
55
56import Servant.API
56import Servant.API
57import Servant.API.Internal.Test.ComprehensiveAPI
57import Servant.API.Internal.Test.ComprehensiveAPI
@@ -82,19 +82,8 @@ data Person = Person {
82instance ToJSON Person
82instance ToJSON Person
83instance FromJSON Person
83instance FromJSON Person
84
84
85−instance ToFormUrlEncoded Person where
85+instance ToForm Person where
86− toFormUrlEncoded Person{..} =
87− [("name", T.pack name), ("age", T.pack (show age))]
88−
89−lookupEither :: (Show a, Eq a) => a -> [(a,b)] -> Either String b
90−lookupEither x xs = do
91− maybe (Left $ "could not find key " <> show x) return $ lookup x xs
92−
93−instance FromFormUrlEncoded Person where
86+instance FromForm Person where
94− fromFormUrlEncoded xs = do
95− n <- lookupEither "name" xs
96− a <- lookupEither "age" xs
97− return $ Person (T.unpack n) (read $ T.unpack a)
98
87
99alice :: Person
88alice :: Person
100alice = Person "Alice" 42
89alice = Person "Alice" 42
servant-server/servant-server.cabal+1−1
@@ -52,7 +52,7 @@ library
52 , base64-bytestring >= 1.0 && < 1.1
52 , base64-bytestring >= 1.0 && < 1.1
53 , bytestring >= 0.10 && < 0.11
53 , bytestring >= 0.10 && < 0.11
54 , containers >= 0.5 && < 0.6
54 , containers >= 0.5 && < 0.6
55− , http-api-data >= 0.1 && < 0.3
55+ , http-api-data >= 0.3 && < 0.4
56 , http-types >= 0.8 && < 0.10
56 , http-types >= 0.8 && < 0.10
57 , network-uri >= 2.6 && < 2.7
57 , network-uri >= 2.6 && < 2.7
58 , mtl >= 2 && < 2.3
58 , mtl >= 2 && < 2.3
servant-server/src/Servant/Server/Internal.hs+1−3
@@ -42,12 +42,10 @@ import Network.Wai (Application, Request, Response,
42 responseLBS, vault)
42 responseLBS, vault)
43import Prelude ()
43import Prelude ()
44import Prelude.Compat
44import Prelude.Compat
45−import Web.HttpApiData (FromHttpApiData)
45+import Web.HttpApiData (FromHttpApiData, parseHeaderMaybe,
46−import Web.HttpApiData.Internal (parseHeaderMaybe,
47 parseQueryParamMaybe,
46 parseQueryParamMaybe,
48 parseUrlPieceMaybe,
47 parseUrlPieceMaybe,
49 parseUrlPieces)
48 parseUrlPieces)
50−
51import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
49import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
52 CaptureAll, Verb,
50 CaptureAll, Verb,
53 ReflectMethod(reflectMethod),
51 ReflectMethod(reflectMethod),
servant/CHANGELOG.md+2−1
@@ -1,12 +1,13 @@
1next
1next
2----
2----
3* Added Eq, Show, Read, Generic and Ord instances to IsSecure
3* Added Eq, Show, Read, Generic and Ord instances to IsSecure
4+* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo`
5+* BACKWARD INCOMPATIBLE: Moved `From/ToFormUrlEncoded` classes, which were renamed to `From/ToForm` to `http-api-data`
4
6
50.8.1
70.8.1
6----
8----
7
9
8* Add `CaptureAll` combinator. Captures all of the remaining segments in a URL.
10* Add `CaptureAll` combinator. Captures all of the remaining segments in a URL.
9−* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo`
10
11
110.8
120.8
12---
13---
servant/servant.cabal+1−1
@@ -55,7 +55,7 @@ library
55 , attoparsec >= 0.12 && < 0.14
55 , attoparsec >= 0.12 && < 0.14
56 , bytestring >= 0.10 && < 0.11
56 , bytestring >= 0.10 && < 0.11
57 , case-insensitive >= 1.2 && < 1.3
57 , case-insensitive >= 1.2 && < 1.3
58− , http-api-data >= 0.1 && < 0.3
58+ , http-api-data >= 0.3 && < 0.4
59 , http-media >= 0.4 && < 0.7
59 , http-media >= 0.4 && < 0.7
60 , http-types >= 0.8 && < 0.10
60 , http-types >= 0.8 && < 0.10
61 , mtl >= 2.0 && < 2.3
61 , mtl >= 2.0 && < 2.3
servant/src/Servant/API.hs+2−2
@@ -62,10 +62,10 @@ import Servant.API.Alternative ((:<|>) (..))
62import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..))
62import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..))
63import Servant.API.Capture (Capture, CaptureAll)
63import Servant.API.Capture (Capture, CaptureAll)
64import Servant.API.ContentTypes (Accept (..), FormUrlEncoded,
64import Servant.API.ContentTypes (Accept (..), FormUrlEncoded,
65− FromFormUrlEncoded (..), JSON,
65+ JSON,
66 MimeRender (..), NoContent (NoContent),
66 MimeRender (..), NoContent (NoContent),
67 MimeUnrender (..), OctetStream,
67 MimeUnrender (..), OctetStream,
68− PlainText, ToFormUrlEncoded (..))
68+ PlainText)
69import Servant.API.Experimental.Auth (AuthProtect)
69import Servant.API.Experimental.Auth (AuthProtect)
70import Servant.API.Header (Header (..))
70import Servant.API.Header (Header (..))
71import Servant.API.HttpVersion (HttpVersion (..))
71import Servant.API.HttpVersion (HttpVersion (..))
servant/src/Servant/API/ContentTypes.hs+9−55
@@ -66,8 +66,6 @@ module Servant.API.ContentTypes
66 , AllMime(..)
66 , AllMime(..)
67 , AllMimeRender(..)
67 , AllMimeRender(..)
68 , AllMimeUnrender(..)
68 , AllMimeUnrender(..)
69− , FromFormUrlEncoded(..)
70− , ToFormUrlEncoded(..)
71 , eitherDecodeLenient
69 , eitherDecodeLenient
72 , canHandleAcceptH
70 , canHandleAcceptH
73 ) where
71 ) where
@@ -82,10 +80,8 @@ import Data.Attoparsec.ByteString.Char8 (endOfInput, parseOnly,
82import qualified Data.ByteString as BS
80import qualified Data.ByteString as BS
83import Data.ByteString.Lazy (ByteString, fromStrict,
81import Data.ByteString.Lazy (ByteString, fromStrict,
84 toStrict)
82 toStrict)
85−import qualified Data.ByteString.Lazy as B
86import qualified Data.ByteString.Lazy.Char8 as BC
83import qualified Data.ByteString.Lazy.Char8 as BC
87import Data.Maybe (isJust)
84import Data.Maybe (isJust)
88−import Data.Monoid.Compat
89import Data.String.Conversions (cs)
85import Data.String.Conversions (cs)
90import qualified Data.Text as TextS
86import qualified Data.Text as TextS
91import qualified Data.Text.Encoding as TextS
87import qualified Data.Text.Encoding as TextS
@@ -94,8 +90,9 @@ import qualified Data.Text.Lazy.Encoding as TextL
94import Data.Typeable
90import Data.Typeable
95import GHC.Generics (Generic)
91import GHC.Generics (Generic)
96import qualified Network.HTTP.Media as M
92import qualified Network.HTTP.Media as M
97−import Network.URI (escapeURIString,
93+import Web.FormUrlEncoded (FromForm, ToForm,
98− isUnreserved, unEscapeString)
94+ urlEncodeAsForm,
95+ urlDecodeAsForm)
99import Prelude ()
96import Prelude ()
100import Prelude.Compat
97import Prelude.Compat
101
98
@@ -290,12 +287,12 @@ instance OVERLAPPABLE_
290 ToJSON a => MimeRender JSON a where
287 ToJSON a => MimeRender JSON a where
291 mimeRender _ = encode
288 mimeRender _ = encode
292
289
293−-- | @encodeFormUrlEncoded . toFormUrlEncoded@
290+-- | @urlEncodeAsForm@
294-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
291-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
295-- holds if every element of x is non-null (i.e., not @("", "")@)
292-- holds if every element of x is non-null (i.e., not @("", "")@)
296instance OVERLAPPABLE_
293instance OVERLAPPABLE_
297− ToFormUrlEncoded a => MimeRender FormUrlEncoded a where
294+ ToForm a => MimeRender FormUrlEncoded a where
298− mimeRender _ = encodeFormUrlEncoded . toFormUrlEncoded
295+ mimeRender _ = urlEncodeAsForm
299
296
300-- | `TextL.encodeUtf8`
297-- | `TextL.encodeUtf8`
301instance MimeRender PlainText TextL.Text where
298instance MimeRender PlainText TextL.Text where
@@ -348,11 +345,11 @@ eitherDecodeLenient input =
348instance FromJSON a => MimeUnrender JSON a where
345instance FromJSON a => MimeUnrender JSON a where
349 mimeUnrender _ = eitherDecodeLenient
346 mimeUnrender _ = eitherDecodeLenient
350
347
351−-- | @decodeFormUrlEncoded >=> fromFormUrlEncoded@
348+-- | @urlDecodeAsForm@
352-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
349-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
353-- holds if every element of x is non-null (i.e., not @("", "")@)
350-- holds if every element of x is non-null (i.e., not @("", "")@)
354−instance FromFormUrlEncoded a => MimeUnrender FormUrlEncoded a where
351+instance FromForm a => MimeUnrender FormUrlEncoded a where
355− mimeUnrender _ = decodeFormUrlEncoded >=> fromFormUrlEncoded
352+ mimeUnrender _ = left TextS.unpack . urlDecodeAsForm
356
353
357-- | @left show . TextL.decodeUtf8'@
354-- | @left show . TextL.decodeUtf8'@
358instance MimeUnrender PlainText TextL.Text where
355instance MimeUnrender PlainText TextL.Text where
@@ -375,49 +372,6 @@ instance MimeUnrender OctetStream BS.ByteString where
375 mimeUnrender _ = Right . toStrict
372 mimeUnrender _ = Right . toStrict
376
373
377
374
378−--------------------------------------------------------------------------
379−-- * FormUrlEncoded
380−
381−-- | A type that can be converted to @application/x-www-form-urlencoded@
382−class ToFormUrlEncoded a where
383− toFormUrlEncoded :: a -> [(TextS.Text, TextS.Text)]
384−
385−instance ToFormUrlEncoded [(TextS.Text, TextS.Text)] where
386− toFormUrlEncoded = id
387−
388−-- | A type that can be converted from @application/x-www-form-urlencoded@,
389−-- with the possibility of failure.
390−class FromFormUrlEncoded a where
391− fromFormUrlEncoded :: [(TextS.Text, TextS.Text)] -> Either String a
392−
393−instance FromFormUrlEncoded [(TextS.Text, TextS.Text)] where
394− fromFormUrlEncoded = return
395−
396−encodeFormUrlEncoded :: [(TextS.Text, TextS.Text)] -> ByteString
397−encodeFormUrlEncoded xs =
398− let escape :: TextS.Text -> ByteString
399− escape = cs . escapeURIString isUnreserved . cs
400− encodePair :: (TextS.Text, TextS.Text) -> ByteString
401− encodePair (k, "") = escape k
402− encodePair (k, v) = escape k <> "=" <> escape v
403− in B.intercalate "&" $ map encodePair xs
404−
405−decodeFormUrlEncoded :: ByteString -> Either String [(TextS.Text, TextS.Text)]
406−decodeFormUrlEncoded "" = return []
407−decodeFormUrlEncoded q = do
408− let xs :: [TextS.Text]
409− xs = TextS.splitOn "&" . cs $ q
410− parsePair :: TextS.Text -> Either String (TextS.Text, TextS.Text)
411− parsePair p =
412− case TextS.splitOn "=" p of
413− [k,v] -> return ( unescape k
414− , unescape v
415− )
416− [k] -> return ( unescape k, "" )
417− _ -> Left $ "not a valid pair: " <> cs p
418− unescape :: TextS.Text -> TextS.Text
419− unescape = cs . unEscapeString . cs . TextS.intercalate "%20" . TextS.splitOn "+"
420− mapM parsePair xs
421
375
422-- $setup
376-- $setup
423-- >>> import Servant.API
377-- >>> import Servant.API
servant/test/Servant/API/ContentTypesSpec.hs−17
@@ -11,7 +11,6 @@ module Servant.API.ContentTypesSpec where
11import Prelude ()
11import Prelude ()
12import Prelude.Compat
12import Prelude.Compat
13
13
14−import Control.Arrow
15import Data.Aeson
14import Data.Aeson
16import Data.ByteString.Char8 (ByteString, append, pack)
15import Data.ByteString.Char8 (ByteString, append, pack)
17import qualified Data.ByteString.Lazy as BSL
16import qualified Data.ByteString.Lazy as BSL
@@ -25,7 +24,6 @@ import Data.String.Conversions (cs)
25import qualified Data.Text as TextS
24import qualified Data.Text as TextS
26import qualified Data.Text.Lazy as TextL
25import qualified Data.Text.Lazy as TextL
27import GHC.Generics
26import GHC.Generics
28−import Network.URL (exportParams, importParams)
29import Test.Hspec
27import Test.Hspec
30import Test.QuickCheck
28import Test.QuickCheck
31import "quickcheck-instances" Test.QuickCheck.Instances ()
29import "quickcheck-instances" Test.QuickCheck.Instances ()
@@ -68,21 +66,6 @@ spec = describe "Servant.API.ContentTypes" $ do
68 it "has mimeUnrender reverse mimeRender for valid top-level json " $ do
66 it "has mimeUnrender reverse mimeRender for valid top-level json " $ do
69 property $ \x -> mimeUnrender p (mimeRender p x) == Right (x::SomeData)
67 property $ \x -> mimeUnrender p (mimeRender p x) == Right (x::SomeData)
70
68
71− describe "The FormUrlEncoded Content-Type type" $ do
72− let p = Proxy :: Proxy FormUrlEncoded
73−
74− it "has mimeUnrender reverse mimeRender" $ do
75− property $ \x -> mempty `notElem` x
76− ==> mimeUnrender p (mimeRender p x) == Right (x::[(TextS.Text,TextS.Text)])
77−
78− it "has mimeUnrender reverse exportParams (Network.URL)" $ do
79− property $ \x -> mempty `notElem` x
80− ==> (mimeUnrender p . cs . exportParams . map (cs *** cs) $ x) == Right (x::[(TextS.Text,TextS.Text)])
81−
82− it "has importParams (Network.URL) reverse mimeRender" $ do
83− property $ \x -> mempty `notElem` x
84− ==> (fmap (map (cs *** cs)) . importParams . cs . mimeRender p $ x) == Just (x::[(TextS.Text,TextS.Text)])
85−
86 describe "The PlainText Content-Type type" $ do
69 describe "The PlainText Content-Type type" $ do
87 let p = Proxy :: Proxy PlainText
70 let p = Proxy :: Proxy PlainText
88
71
stack-ghc-7.8.4.yaml+2−1
@@ -15,9 +15,10 @@ extra-deps:
15- hspec-core-2.2.3
15- hspec-core-2.2.3
16- hspec-discover-2.2.3
16- hspec-discover-2.2.3
17- hspec-expectations-0.7.2
17- hspec-expectations-0.7.2
18−- http-api-data-0.2.2
18+- http-api-data-0.3
19- primitive-0.6.1.0
19- primitive-0.6.1.0
20- should-not-typecheck-2.1.0
20- should-not-typecheck-2.1.0
21- time-locale-compat-0.1.1.1
21- time-locale-compat-0.1.1.1
22+- uri-bytestring-0.2.2.0
22- wai-app-static-3.1.5
23- wai-app-static-3.1.5
23resolver: lts-2.22
24resolver: lts-2.22
stack-ghc-8.0.1.yaml+3−1
@@ -6,5 +6,7 @@ packages:
6- servant-foreign/
6- servant-foreign/
7- servant-js/
7- servant-js/
8- servant-server/
8- servant-server/
9−extra-deps: []
9+extra-deps:
10+- http-api-data-0.3
11+- uri-bytestring-0.2.2.0
10flags: {}
12flags: {}
stack.yaml+1
@@ -8,4 +8,5 @@ packages:
8- servant-server/
8- servant-server/
9- doc/tutorial
9- doc/tutorial
10extra-deps:
10extra-deps:
11+- http-api-data-0.3
11resolver: lts-6.0
12resolver: lts-6.0