Merge pull request #592 from haskell-servant/use-http-api-forms

Use http api forms
This commit is contained in:
Julian Arni 2016-09-13 10:30:34 -03:00 committed by GitHub
commit a274d8a124
12 changed files with 27 additions and 97 deletions

View File

@ -41,7 +41,7 @@ library
, base64-bytestring >= 1.0.0.1 && < 1.1 , base64-bytestring >= 1.0.0.1 && < 1.1
, bytestring >= 0.10 && < 0.11 , bytestring >= 0.10 && < 0.11
, exceptions >= 0.8 && < 0.9 , exceptions >= 0.8 && < 0.9
, http-api-data >= 0.1 && < 0.3 , http-api-data >= 0.3 && < 0.4
, http-client >= 0.4.18.1 && < 0.6 , http-client >= 0.4.18.1 && < 0.6
, http-client-tls >= 0.2.2 && < 0.4 , http-client-tls >= 0.2.2 && < 0.4
, http-media >= 0.6.2 && < 0.7 , http-media >= 0.6.2 && < 0.7
@ -78,6 +78,7 @@ test-suite spec
, bytestring , bytestring
, deepseq , deepseq
, hspec == 2.* , hspec == 2.*
, http-api-data
, http-client , http-client
, http-media , http-media
, http-types , http-types

View File

@ -39,7 +39,6 @@ import Data.Char (chr, isPrint)
import Data.Foldable (forM_) import Data.Foldable (forM_)
import Data.Monoid hiding (getLast) import Data.Monoid hiding (getLast)
import Data.Proxy import Data.Proxy
import qualified Data.Text as T
import GHC.Generics (Generic) import GHC.Generics (Generic)
import qualified Network.HTTP.Client as C import qualified Network.HTTP.Client as C
import Network.HTTP.Media import Network.HTTP.Media
@ -52,6 +51,7 @@ import Test.Hspec
import Test.Hspec.QuickCheck import Test.Hspec.QuickCheck
import Test.HUnit import Test.HUnit
import Test.QuickCheck import Test.QuickCheck
import Web.FormUrlEncoded (FromForm, ToForm)
import Servant.API import Servant.API
import Servant.API.Internal.Test.ComprehensiveAPI import Servant.API.Internal.Test.ComprehensiveAPI
@ -82,19 +82,8 @@ data Person = Person {
instance ToJSON Person instance ToJSON Person
instance FromJSON Person instance FromJSON Person
instance ToFormUrlEncoded Person where instance ToForm Person where
toFormUrlEncoded Person{..} = instance FromForm Person where
[("name", T.pack name), ("age", T.pack (show age))]
lookupEither :: (Show a, Eq a) => a -> [(a,b)] -> Either String b
lookupEither x xs = do
maybe (Left $ "could not find key " <> show x) return $ lookup x xs
instance FromFormUrlEncoded Person where
fromFormUrlEncoded xs = do
n <- lookupEither "name" xs
a <- lookupEither "age" xs
return $ Person (T.unpack n) (read $ T.unpack a)
alice :: Person alice :: Person
alice = Person "Alice" 42 alice = Person "Alice" 42

View File

@ -52,7 +52,7 @@ library
, base64-bytestring >= 1.0 && < 1.1 , base64-bytestring >= 1.0 && < 1.1
, bytestring >= 0.10 && < 0.11 , bytestring >= 0.10 && < 0.11
, containers >= 0.5 && < 0.6 , containers >= 0.5 && < 0.6
, http-api-data >= 0.1 && < 0.3 , http-api-data >= 0.3 && < 0.4
, http-types >= 0.8 && < 0.10 , http-types >= 0.8 && < 0.10
, network-uri >= 2.6 && < 2.7 , network-uri >= 2.6 && < 2.7
, mtl >= 2 && < 2.3 , mtl >= 2 && < 2.3

View File

@ -42,12 +42,10 @@ import Network.Wai (Application, Request, Response,
responseLBS, vault) responseLBS, vault)
import Prelude () import Prelude ()
import Prelude.Compat import Prelude.Compat
import Web.HttpApiData (FromHttpApiData) import Web.HttpApiData (FromHttpApiData, parseHeaderMaybe,
import Web.HttpApiData.Internal (parseHeaderMaybe,
parseQueryParamMaybe, parseQueryParamMaybe,
parseUrlPieceMaybe, parseUrlPieceMaybe,
parseUrlPieces) parseUrlPieces)
import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture, import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
CaptureAll, Verb, CaptureAll, Verb,
ReflectMethod(reflectMethod), ReflectMethod(reflectMethod),

View File

@ -1,12 +1,13 @@
next next
---- ----
* Added Eq, Show, Read, Generic and Ord instances to IsSecure * Added Eq, Show, Read, Generic and Ord instances to IsSecure
* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo`
* BACKWARD INCOMPATIBLE: Moved `From/ToFormUrlEncoded` classes, which were renamed to `From/ToForm` to `http-api-data`
0.8.1 0.8.1
---- ----
* Add `CaptureAll` combinator. Captures all of the remaining segments in a URL. * Add `CaptureAll` combinator. Captures all of the remaining segments in a URL.
* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo`
0.8 0.8
--- ---

View File

@ -55,7 +55,7 @@ library
, attoparsec >= 0.12 && < 0.14 , attoparsec >= 0.12 && < 0.14
, bytestring >= 0.10 && < 0.11 , bytestring >= 0.10 && < 0.11
, case-insensitive >= 1.2 && < 1.3 , case-insensitive >= 1.2 && < 1.3
, http-api-data >= 0.1 && < 0.3 , http-api-data >= 0.3 && < 0.4
, http-media >= 0.4 && < 0.7 , http-media >= 0.4 && < 0.7
, http-types >= 0.8 && < 0.10 , http-types >= 0.8 && < 0.10
, mtl >= 2.0 && < 2.3 , mtl >= 2.0 && < 2.3

View File

@ -62,10 +62,10 @@ import Servant.API.Alternative ((:<|>) (..))
import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..)) import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..))
import Servant.API.Capture (Capture, CaptureAll) import Servant.API.Capture (Capture, CaptureAll)
import Servant.API.ContentTypes (Accept (..), FormUrlEncoded, import Servant.API.ContentTypes (Accept (..), FormUrlEncoded,
FromFormUrlEncoded (..), JSON, JSON,
MimeRender (..), NoContent (NoContent), MimeRender (..), NoContent (NoContent),
MimeUnrender (..), OctetStream, MimeUnrender (..), OctetStream,
PlainText, ToFormUrlEncoded (..)) PlainText)
import Servant.API.Experimental.Auth (AuthProtect) import Servant.API.Experimental.Auth (AuthProtect)
import Servant.API.Header (Header (..)) import Servant.API.Header (Header (..))
import Servant.API.HttpVersion (HttpVersion (..)) import Servant.API.HttpVersion (HttpVersion (..))

View File

@ -66,8 +66,6 @@ module Servant.API.ContentTypes
, AllMime(..) , AllMime(..)
, AllMimeRender(..) , AllMimeRender(..)
, AllMimeUnrender(..) , AllMimeUnrender(..)
, FromFormUrlEncoded(..)
, ToFormUrlEncoded(..)
, eitherDecodeLenient , eitherDecodeLenient
, canHandleAcceptH , canHandleAcceptH
) where ) where
@ -82,10 +80,8 @@ import Data.Attoparsec.ByteString.Char8 (endOfInput, parseOnly,
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.ByteString.Lazy (ByteString, fromStrict, import Data.ByteString.Lazy (ByteString, fromStrict,
toStrict) toStrict)
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.Lazy.Char8 as BC import qualified Data.ByteString.Lazy.Char8 as BC
import Data.Maybe (isJust) import Data.Maybe (isJust)
import Data.Monoid.Compat
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import qualified Data.Text as TextS import qualified Data.Text as TextS
import qualified Data.Text.Encoding as TextS import qualified Data.Text.Encoding as TextS
@ -94,8 +90,9 @@ import qualified Data.Text.Lazy.Encoding as TextL
import Data.Typeable import Data.Typeable
import GHC.Generics (Generic) import GHC.Generics (Generic)
import qualified Network.HTTP.Media as M import qualified Network.HTTP.Media as M
import Network.URI (escapeURIString, import Web.FormUrlEncoded (FromForm, ToForm,
isUnreserved, unEscapeString) urlEncodeAsForm,
urlDecodeAsForm)
import Prelude () import Prelude ()
import Prelude.Compat import Prelude.Compat
@ -290,12 +287,12 @@ instance OVERLAPPABLE_
ToJSON a => MimeRender JSON a where ToJSON a => MimeRender JSON a where
mimeRender _ = encode mimeRender _ = encode
-- | @encodeFormUrlEncoded . toFormUrlEncoded@ -- | @urlEncodeAsForm@
-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only -- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
-- holds if every element of x is non-null (i.e., not @("", "")@) -- holds if every element of x is non-null (i.e., not @("", "")@)
instance OVERLAPPABLE_ instance OVERLAPPABLE_
ToFormUrlEncoded a => MimeRender FormUrlEncoded a where ToForm a => MimeRender FormUrlEncoded a where
mimeRender _ = encodeFormUrlEncoded . toFormUrlEncoded mimeRender _ = urlEncodeAsForm
-- | `TextL.encodeUtf8` -- | `TextL.encodeUtf8`
instance MimeRender PlainText TextL.Text where instance MimeRender PlainText TextL.Text where
@ -348,11 +345,11 @@ eitherDecodeLenient input =
instance FromJSON a => MimeUnrender JSON a where instance FromJSON a => MimeUnrender JSON a where
mimeUnrender _ = eitherDecodeLenient mimeUnrender _ = eitherDecodeLenient
-- | @decodeFormUrlEncoded >=> fromFormUrlEncoded@ -- | @urlDecodeAsForm@
-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only -- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
-- holds if every element of x is non-null (i.e., not @("", "")@) -- holds if every element of x is non-null (i.e., not @("", "")@)
instance FromFormUrlEncoded a => MimeUnrender FormUrlEncoded a where instance FromForm a => MimeUnrender FormUrlEncoded a where
mimeUnrender _ = decodeFormUrlEncoded >=> fromFormUrlEncoded mimeUnrender _ = left TextS.unpack . urlDecodeAsForm
-- | @left show . TextL.decodeUtf8'@ -- | @left show . TextL.decodeUtf8'@
instance MimeUnrender PlainText TextL.Text where instance MimeUnrender PlainText TextL.Text where
@ -375,49 +372,6 @@ instance MimeUnrender OctetStream BS.ByteString where
mimeUnrender _ = Right . toStrict mimeUnrender _ = Right . toStrict
--------------------------------------------------------------------------
-- * FormUrlEncoded
-- | A type that can be converted to @application/x-www-form-urlencoded@
class ToFormUrlEncoded a where
toFormUrlEncoded :: a -> [(TextS.Text, TextS.Text)]
instance ToFormUrlEncoded [(TextS.Text, TextS.Text)] where
toFormUrlEncoded = id
-- | A type that can be converted from @application/x-www-form-urlencoded@,
-- with the possibility of failure.
class FromFormUrlEncoded a where
fromFormUrlEncoded :: [(TextS.Text, TextS.Text)] -> Either String a
instance FromFormUrlEncoded [(TextS.Text, TextS.Text)] where
fromFormUrlEncoded = return
encodeFormUrlEncoded :: [(TextS.Text, TextS.Text)] -> ByteString
encodeFormUrlEncoded xs =
let escape :: TextS.Text -> ByteString
escape = cs . escapeURIString isUnreserved . cs
encodePair :: (TextS.Text, TextS.Text) -> ByteString
encodePair (k, "") = escape k
encodePair (k, v) = escape k <> "=" <> escape v
in B.intercalate "&" $ map encodePair xs
decodeFormUrlEncoded :: ByteString -> Either String [(TextS.Text, TextS.Text)]
decodeFormUrlEncoded "" = return []
decodeFormUrlEncoded q = do
let xs :: [TextS.Text]
xs = TextS.splitOn "&" . cs $ q
parsePair :: TextS.Text -> Either String (TextS.Text, TextS.Text)
parsePair p =
case TextS.splitOn "=" p of
[k,v] -> return ( unescape k
, unescape v
)
[k] -> return ( unescape k, "" )
_ -> Left $ "not a valid pair: " <> cs p
unescape :: TextS.Text -> TextS.Text
unescape = cs . unEscapeString . cs . TextS.intercalate "%20" . TextS.splitOn "+"
mapM parsePair xs
-- $setup -- $setup
-- >>> import Servant.API -- >>> import Servant.API

View File

@ -11,7 +11,6 @@ module Servant.API.ContentTypesSpec where
import Prelude () import Prelude ()
import Prelude.Compat import Prelude.Compat
import Control.Arrow
import Data.Aeson import Data.Aeson
import Data.ByteString.Char8 (ByteString, append, pack) import Data.ByteString.Char8 (ByteString, append, pack)
import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy as BSL
@ -25,7 +24,6 @@ import Data.String.Conversions (cs)
import qualified Data.Text as TextS import qualified Data.Text as TextS
import qualified Data.Text.Lazy as TextL import qualified Data.Text.Lazy as TextL
import GHC.Generics import GHC.Generics
import Network.URL (exportParams, importParams)
import Test.Hspec import Test.Hspec
import Test.QuickCheck import Test.QuickCheck
import "quickcheck-instances" Test.QuickCheck.Instances () import "quickcheck-instances" Test.QuickCheck.Instances ()
@ -68,21 +66,6 @@ spec = describe "Servant.API.ContentTypes" $ do
it "has mimeUnrender reverse mimeRender for valid top-level json " $ do it "has mimeUnrender reverse mimeRender for valid top-level json " $ do
property $ \x -> mimeUnrender p (mimeRender p x) == Right (x::SomeData) property $ \x -> mimeUnrender p (mimeRender p x) == Right (x::SomeData)
describe "The FormUrlEncoded Content-Type type" $ do
let p = Proxy :: Proxy FormUrlEncoded
it "has mimeUnrender reverse mimeRender" $ do
property $ \x -> mempty `notElem` x
==> mimeUnrender p (mimeRender p x) == Right (x::[(TextS.Text,TextS.Text)])
it "has mimeUnrender reverse exportParams (Network.URL)" $ do
property $ \x -> mempty `notElem` x
==> (mimeUnrender p . cs . exportParams . map (cs *** cs) $ x) == Right (x::[(TextS.Text,TextS.Text)])
it "has importParams (Network.URL) reverse mimeRender" $ do
property $ \x -> mempty `notElem` x
==> (fmap (map (cs *** cs)) . importParams . cs . mimeRender p $ x) == Just (x::[(TextS.Text,TextS.Text)])
describe "The PlainText Content-Type type" $ do describe "The PlainText Content-Type type" $ do
let p = Proxy :: Proxy PlainText let p = Proxy :: Proxy PlainText

View File

@ -15,9 +15,10 @@ extra-deps:
- hspec-core-2.2.3 - hspec-core-2.2.3
- hspec-discover-2.2.3 - hspec-discover-2.2.3
- hspec-expectations-0.7.2 - hspec-expectations-0.7.2
- http-api-data-0.2.2 - http-api-data-0.3
- primitive-0.6.1.0 - primitive-0.6.1.0
- should-not-typecheck-2.1.0 - should-not-typecheck-2.1.0
- time-locale-compat-0.1.1.1 - time-locale-compat-0.1.1.1
- uri-bytestring-0.2.2.0
- wai-app-static-3.1.5 - wai-app-static-3.1.5
resolver: lts-2.22 resolver: lts-2.22

View File

@ -6,5 +6,7 @@ packages:
- servant-foreign/ - servant-foreign/
- servant-js/ - servant-js/
- servant-server/ - servant-server/
extra-deps: [] extra-deps:
- http-api-data-0.3
- uri-bytestring-0.2.2.0
flags: {} flags: {}

View File

@ -8,4 +8,5 @@ packages:
- servant-server/ - servant-server/
- doc/tutorial - doc/tutorial
extra-deps: extra-deps:
- http-api-data-0.3
resolver: lts-6.0 resolver: lts-6.0