Merge remote-tracking branch 'origin' into tutorial

This commit is contained in:
Sönke Hahn 2016-02-27 16:51:34 +01:00
commit 57d0261fdf
25 changed files with 292 additions and 277 deletions

1
.ghci
View file

@ -1 +0,0 @@
:set -itest -isrc -packagehspec2

View file

@ -6,7 +6,7 @@ HEAD
* Added support for `path` on `BaseUrl`. * Added support for `path` on `BaseUrl`.
* `client` now takes an explicit `Manager` argument. * `client` now takes an explicit `Manager` argument.
* Use `http-api-data` instead of `Servant.Common.Text` * Use `http-api-data` instead of `Servant.Common.Text`
* Client functions now consider any 2xx succesful. * Client functions now consider any 2xx successful.
* Remove matrix params. * Remove matrix params.
0.4.1 0.4.1

View file

@ -115,7 +115,7 @@ api :: Proxy Api
api = Proxy api = Proxy
server :: Application server :: Application
server = serve api EmptyConfig ( server = serve api (
return alice return alice
:<|> return NoContent :<|> return NoContent
:<|> (\ name -> return $ Person name 0) :<|> (\ name -> return $ Person name 0)
@ -142,7 +142,7 @@ failApi :: Proxy FailApi
failApi = Proxy failApi = Proxy
failServer :: Application failServer :: Application
failServer = serve failApi EmptyConfig ( failServer = serve failApi (
(\ _request respond -> respond $ responseLBS ok200 [] "") (\ _request respond -> respond $ responseLBS ok200 [] "")
:<|> (\ _capture _request respond -> respond $ responseLBS ok200 [("content-type", "application/json")] "") :<|> (\ _capture _request respond -> respond $ responseLBS ok200 [("content-type", "application/json")] "")
:<|> (\_request respond -> respond $ responseLBS ok200 [("content-type", "fooooo")] "") :<|> (\_request respond -> respond $ responseLBS ok200 [("content-type", "fooooo")] "")
@ -232,7 +232,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
wrappedApiSpec :: Spec wrappedApiSpec :: Spec
wrappedApiSpec = describe "error status codes" $ do wrappedApiSpec = describe "error status codes" $ do
let serveW api = serve api EmptyConfig $ throwE $ ServantErr 500 "error message" "" [] let serveW api = serve api $ throwE $ ServantErr 500 "error message" "" []
context "are correctly handled by the client" $ context "are correctly handled by the client" $
let test :: (WrappedApi, String) -> Spec let test :: (WrappedApi, String) -> Spec
test (WrappedApi api, desc) = test (WrappedApi api, desc) =

View file

@ -2,3 +2,4 @@ HEAD
----- -----
* Use the `text` package instead of `String`. * Use the `text` package instead of `String`.
* Extract javascript-oblivious types and helpers to *servant-foreign* * Extract javascript-oblivious types and helpers to *servant-foreign*
* Typed-languages support

View file

@ -1,36 +1,50 @@
-- | Generalizes all the data needed to make code generation work with -- | Generalizes all the data needed to make code generation work with
-- arbitrary programming languages. -- arbitrary programming languages.
module Servant.Foreign module Servant.Foreign
( HasForeign(..) ( ArgType(..)
, HasForeignType(..) , HeaderArg(..)
, QueryArg(..)
, Req(..)
, Segment(..) , Segment(..)
, SegmentType(..) , SegmentType(..)
, Url(..)
-- aliases
, Path
, ForeignType
, Arg
, FunctionName , FunctionName
, QueryArg(..) -- lenses
, HeaderArg(..) , reqUrl
, ArgType(..) , reqMethod
, Req , reqHeaders
, reqBody
, reqReturnType
, reqFuncName
, path
, queryStr
, argName
, argType
-- prisms
, _HeaderArg
, _ReplaceHeaderArg
, _Static
, _Cap
, _Normal
, _Flag
, _List
-- rest of it
, HasForeign(..)
, HasForeignType(..)
, HasNoForeignType
, GenerateList(..)
, NoTypes
, captureArg , captureArg
, defReq , isCapture
, concatCase , concatCase
, snakeCase , snakeCase
, camelCase , camelCase
-- lenses , defReq
, argType
, argName
, isCapture
, funcName
, path
, reqUrl
, reqBody
, reqHeaders
, reqMethod
, reqReturnType
, segment
, queryStr
, listFromAPI , listFromAPI
, GenerateList(..)
, NoTypes
-- re-exports -- re-exports
, module Servant.API , module Servant.API
) where ) where

View file

@ -19,17 +19,19 @@
-- arbitrary programming languages. -- arbitrary programming languages.
module Servant.Foreign.Internal where module Servant.Foreign.Internal where
import Control.Lens (makeLenses, (%~), (&), (.~), (<>~)) import Control.Lens (makeLenses, makePrisms, (%~), (&), (.~), (<>~))
import qualified Data.Char as C import qualified Data.Char as C
import Data.Proxy import Data.Proxy
import Data.Text import Data.Text
import Data.Text.Encoding (decodeUtf8) import Data.Text.Encoding (decodeUtf8)
import GHC.Exts (Constraint) import GHC.Exts (Constraint)
import GHC.TypeLits import GHC.TypeLits
import qualified Network.HTTP.Types as HTTP import qualified Network.HTTP.Types as HTTP
import Prelude hiding (concat) import Prelude hiding (concat)
import Servant.API import Servant.API
type FunctionName = [Text]
-- | Function name builder that simply concat each part together -- | Function name builder that simply concat each part together
concatCase :: FunctionName -> Text concatCase :: FunctionName -> Text
concatCase = concat concatCase = concat
@ -49,36 +51,50 @@ camelCase = camelCase' . Prelude.map (replace "-" "")
capitalize name = C.toUpper (Data.Text.head name) `cons` Data.Text.tail name capitalize name = C.toUpper (Data.Text.head name) `cons` Data.Text.tail name
type ForeignType = Text type ForeignType = Text
type Arg = (Text, ForeignType) type Arg = (Text, ForeignType)
newtype Segment = Segment { _segment :: SegmentType } data SegmentType
= Static Text
-- ^ a static path segment. like "/foo"
| Cap Arg
-- ^ a capture. like "/:userid"
deriving (Eq, Show) deriving (Eq, Show)
data SegmentType = Static Text -- ^ a static path segment. like "/foo" makePrisms ''SegmentType
| Cap Arg -- ^ a capture. like "/:userid"
newtype Segment = Segment { unSegment :: SegmentType }
deriving (Eq, Show) deriving (Eq, Show)
makePrisms ''Segment
type Path = [Segment] type Path = [Segment]
data ArgType = data ArgType
Normal = Normal
| Flag | Flag
| List | List
deriving (Eq, Show) deriving (Eq, Show)
makePrisms ''ArgType
data QueryArg = QueryArg data QueryArg = QueryArg
{ _argName :: Arg { _argName :: Arg
, _argType :: ArgType , _argType :: ArgType
} deriving (Eq, Show) } deriving (Eq, Show)
data HeaderArg = HeaderArg makeLenses ''QueryArg
{ headerArg :: Arg
}
| ReplaceHeaderArg
{ headerArg :: Arg
, headerPattern :: Text
} deriving (Eq, Show)
data HeaderArg = HeaderArg
{ headerArg :: Arg }
| ReplaceHeaderArg
{ headerArg :: Arg
, headerPattern :: Text
} deriving (Eq, Show)
makeLenses ''HeaderArg
makePrisms ''HeaderArg
data Url = Url data Url = Url
{ _path :: Path { _path :: Path
@ -88,7 +104,7 @@ data Url = Url
defUrl :: Url defUrl :: Url
defUrl = Url [] [] defUrl = Url [] []
type FunctionName = [Text] makeLenses ''Url
data Req = Req data Req = Req
{ _reqUrl :: Url { _reqUrl :: Url
@ -96,12 +112,9 @@ data Req = Req
, _reqHeaders :: [HeaderArg] , _reqHeaders :: [HeaderArg]
, _reqBody :: Maybe ForeignType , _reqBody :: Maybe ForeignType
, _reqReturnType :: ForeignType , _reqReturnType :: ForeignType
, _funcName :: FunctionName , _reqFuncName :: FunctionName
} deriving (Eq, Show) } deriving (Eq, Show)
makeLenses ''QueryArg
makeLenses ''Segment
makeLenses ''Url
makeLenses ''Req makeLenses ''Req
isCapture :: Segment -> Bool isCapture :: Segment -> Bool
@ -155,66 +168,66 @@ type family Elem (a :: *) (ls::[*]) :: Constraint where
-- > -- >
-- --
class HasForeignType lang a where class HasForeignType lang a where
typeFor :: Proxy lang -> Proxy a -> ForeignType typeFor :: Proxy lang -> Proxy a -> ForeignType
data NoTypes data NoTypes
instance HasForeignType NoTypes a where instance HasForeignType NoTypes ftype where
typeFor _ _ = empty typeFor _ _ = empty
type HasNoForeignType = HasForeignType NoTypes
class HasForeign lang (layout :: *) where class HasForeign lang (layout :: *) where
type Foreign layout :: * type Foreign layout :: *
foreignFor :: Proxy lang -> Proxy layout -> Req -> Foreign layout foreignFor :: Proxy lang -> Proxy layout -> Req -> Foreign layout
instance (HasForeign lang a, HasForeign lang b) instance (HasForeign lang a, HasForeign lang b)
=> HasForeign lang (a :<|> b) where => HasForeign lang (a :<|> b) where
type Foreign (a :<|> b) = Foreign a :<|> Foreign b type Foreign (a :<|> b) = Foreign a :<|> Foreign b
foreignFor lang Proxy req = foreignFor lang Proxy req =
foreignFor lang (Proxy :: Proxy a) req foreignFor lang (Proxy :: Proxy a) req
:<|> foreignFor lang (Proxy :: Proxy b) req :<|> foreignFor lang (Proxy :: Proxy b) req
instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout) instance (KnownSymbol sym, HasForeignType lang ftype, HasForeign lang sublayout)
=> HasForeign lang (Capture sym a :> sublayout) where => HasForeign lang (Capture sym ftype :> sublayout) where
type Foreign (Capture sym a :> sublayout) = Foreign sublayout type Foreign (Capture sym a :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
foreignFor lang (Proxy :: Proxy sublayout) $ foreignFor lang (Proxy :: Proxy sublayout) $
req & reqUrl.path <>~ [Segment (Cap arg)] req & reqUrl.path <>~ [Segment (Cap arg)]
& funcName %~ (++ ["by", str]) & reqFuncName %~ (++ ["by", str])
where where
str = pack . symbolVal $ (Proxy :: Proxy sym) str = pack . symbolVal $ (Proxy :: Proxy sym)
arg = (str, typeFor lang (Proxy :: Proxy a)) arg = (str, typeFor lang (Proxy :: Proxy ftype))
instance (Elem JSON list, HasForeignType lang a, ReflectMethod method) instance (Elem JSON list, HasForeignType lang a, ReflectMethod method)
=> HasForeign lang (Verb method status list a) where => HasForeign lang (Verb method status list a) where
type Foreign (Verb method status list a) = Req type Foreign (Verb method status list a) = Req
foreignFor lang Proxy req = foreignFor lang Proxy req =
req & funcName %~ (methodLC :) req & reqFuncName %~ (methodLC :)
& reqMethod .~ method & reqMethod .~ method
& reqReturnType .~ retType & reqReturnType .~ retType
where where
retType = typeFor lang (Proxy :: Proxy a) retType = typeFor lang (Proxy :: Proxy a)
method = reflectMethod (Proxy :: Proxy method) method = reflectMethod (Proxy :: Proxy method)
methodLC = toLower $ decodeUtf8 method methodLC = toLower $ decodeUtf8 method
instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout) instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
=> HasForeign lang (Header sym a :> sublayout) where => HasForeign lang (Header sym a :> sublayout) where
type Foreign (Header sym a :> sublayout) = Foreign sublayout type Foreign (Header sym a :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
foreignFor lang subP $ req foreignFor lang subP $ req
& reqHeaders <>~ [HeaderArg arg] & reqHeaders <>~ [HeaderArg arg]
where where
hname = pack . symbolVal $ (Proxy :: Proxy sym) hname = pack . symbolVal $ (Proxy :: Proxy sym)
arg = (hname, typeFor lang (Proxy :: Proxy a)) arg = (hname, typeFor lang (Proxy :: Proxy a))
subP = Proxy :: Proxy sublayout subP = Proxy :: Proxy sublayout
instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout) instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
=> HasForeign lang (QueryParam sym a :> sublayout) where => HasForeign lang (QueryParam sym a :> sublayout) where
type Foreign (QueryParam sym a :> sublayout) = Foreign sublayout type Foreign (QueryParam sym a :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
@ -222,38 +235,37 @@ instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
req & reqUrl.queryStr <>~ [QueryArg arg Normal] req & reqUrl.queryStr <>~ [QueryArg arg Normal]
where where
str = pack . symbolVal $ (Proxy :: Proxy sym) str = pack . symbolVal $ (Proxy :: Proxy sym)
arg = (str, typeFor lang (Proxy :: Proxy a)) arg = (str, typeFor lang (Proxy :: Proxy a))
instance (KnownSymbol sym, HasForeignType lang [a], HasForeign lang sublayout) instance
=> HasForeign lang (QueryParams sym a :> sublayout) where (KnownSymbol sym, HasForeignType lang [a], HasForeign lang sublayout)
=> HasForeign lang (QueryParams sym a :> sublayout) where
type Foreign (QueryParams sym a :> sublayout) = Foreign sublayout type Foreign (QueryParams sym a :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
foreignFor lang (Proxy :: Proxy sublayout) $ foreignFor lang (Proxy :: Proxy sublayout) $
req & reqUrl.queryStr <>~ [QueryArg arg List] req & reqUrl.queryStr <>~ [QueryArg arg List]
where where
str = pack . symbolVal $ (Proxy :: Proxy sym) str = pack . symbolVal $ (Proxy :: Proxy sym)
arg = (str, typeFor lang (Proxy :: Proxy [a])) arg = (str, typeFor lang (Proxy :: Proxy [a]))
instance (KnownSymbol sym, HasForeignType lang a, a ~ Bool, HasForeign lang sublayout) instance
=> HasForeign lang (QueryFlag sym :> sublayout) where (KnownSymbol sym, HasForeignType lang Bool, HasForeign lang sublayout)
=> HasForeign lang (QueryFlag sym :> sublayout) where
type Foreign (QueryFlag sym :> sublayout) = Foreign sublayout type Foreign (QueryFlag sym :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
foreignFor lang (Proxy :: Proxy sublayout) $ foreignFor lang (Proxy :: Proxy sublayout) $
req & reqUrl.queryStr <>~ [QueryArg arg Flag] req & reqUrl.queryStr <>~ [QueryArg arg Flag]
where where
str = pack . symbolVal $ (Proxy :: Proxy sym) str = pack . symbolVal $ (Proxy :: Proxy sym)
arg = (str, typeFor lang (Proxy :: Proxy a)) arg = (str, typeFor lang (Proxy :: Proxy Bool))
instance HasForeign lang Raw where instance HasForeign lang Raw where
type Foreign Raw = HTTP.Method -> Req type Foreign Raw = HTTP.Method -> Req
foreignFor _ Proxy req method = foreignFor _ Proxy req method =
req & funcName %~ ((toLower $ decodeUtf8 method) :) req & reqFuncName %~ ((toLower $ decodeUtf8 method) :)
& reqMethod .~ method & reqMethod .~ method
instance (Elem JSON list, HasForeignType lang a, HasForeign lang sublayout) instance (Elem JSON list, HasForeignType lang a, HasForeign lang sublayout)
@ -271,19 +283,21 @@ instance (KnownSymbol path, HasForeign lang sublayout)
foreignFor lang Proxy req = foreignFor lang Proxy req =
foreignFor lang (Proxy :: Proxy sublayout) $ foreignFor lang (Proxy :: Proxy sublayout) $
req & reqUrl.path <>~ [Segment (Static str)] req & reqUrl.path <>~ [Segment (Static str)]
& funcName %~ (++ [str]) & reqFuncName %~ (++ [str])
where where
str = Data.Text.map (\c -> if c == '.' then '_' else c) str =
. pack . symbolVal $ (Proxy :: Proxy path) Data.Text.map (\c -> if c == '.' then '_' else c)
. pack . symbolVal $ (Proxy :: Proxy path)
instance HasForeign lang sublayout => HasForeign lang (RemoteHost :> sublayout) where instance HasForeign lang sublayout
=> HasForeign lang (RemoteHost :> sublayout) where
type Foreign (RemoteHost :> sublayout) = Foreign sublayout type Foreign (RemoteHost :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
foreignFor lang (Proxy :: Proxy sublayout) req foreignFor lang (Proxy :: Proxy sublayout) req
instance HasForeign lang sublayout => HasForeign lang (IsSecure :> sublayout) where instance HasForeign lang sublayout
=> HasForeign lang (IsSecure :> sublayout) where
type Foreign (IsSecure :> sublayout) = Foreign sublayout type Foreign (IsSecure :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
@ -302,7 +316,8 @@ instance HasForeign lang sublayout =>
foreignFor lang Proxy = foreignFor lang (Proxy :: Proxy sublayout) foreignFor lang Proxy = foreignFor lang (Proxy :: Proxy sublayout)
instance HasForeign lang sublayout => HasForeign lang (HttpVersion :> sublayout) where instance HasForeign lang sublayout
=> HasForeign lang (HttpVersion :> sublayout) where
type Foreign (HttpVersion :> sublayout) = Foreign sublayout type Foreign (HttpVersion :> sublayout) = Foreign sublayout
foreignFor lang Proxy req = foreignFor lang Proxy req =
@ -317,10 +332,15 @@ class GenerateList reqs where
instance GenerateList Req where instance GenerateList Req where
generateList r = [r] generateList r = [r]
instance (GenerateList start, GenerateList rest) => GenerateList (start :<|> rest) where instance (GenerateList start, GenerateList rest)
=> GenerateList (start :<|> rest) where
generateList (start :<|> rest) = (generateList start) ++ (generateList rest) generateList (start :<|> rest) = (generateList start) ++ (generateList rest)
-- | Generate the necessary data for codegen as a list, each 'Req' -- | Generate the necessary data for codegen as a list, each 'Req'
-- describing one endpoint from your API type. -- describing one endpoint from your API type.
listFromAPI :: (HasForeign lang api, GenerateList (Foreign api)) => Proxy lang -> Proxy api -> [Req] listFromAPI
:: (HasForeign lang api, GenerateList (Foreign api))
=> Proxy lang
-> Proxy api
-> [Req]
listFromAPI lang p = generateList (foreignFor lang p defReq) listFromAPI lang p = generateList (foreignFor lang p defReq)

View file

@ -15,7 +15,6 @@ module Servant.ForeignSpec where
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.Proxy import Data.Proxy
import Servant.Foreign import Servant.Foreign
import Servant.Foreign.Internal
import Test.Hspec import Test.Hspec
@ -35,15 +34,19 @@ camelCaseSpec = describe "camelCase" $ do
data LangX data LangX
instance HasForeignType LangX () where instance HasForeignType LangX () where
typeFor _ _ = "voidX" typeFor _ _ = "voidX"
instance HasForeignType LangX Int where instance HasForeignType LangX Int where
typeFor _ _ = "intX" typeFor _ _ = "intX"
instance HasForeignType LangX Bool where instance HasForeignType LangX Bool where
typeFor _ _ = "boolX" typeFor _ _ = "boolX"
instance OVERLAPPING_ HasForeignType LangX String where instance OVERLAPPING_ HasForeignType LangX String where
typeFor _ _ = "stringX" typeFor _ _ = "stringX"
instance OVERLAPPABLE_ HasForeignType LangX a => HasForeignType LangX [a] where instance OVERLAPPABLE_ HasForeignType LangX a => HasForeignType LangX [a] where
typeFor lang _ = "listX of " <> typeFor lang (Proxy :: Proxy a) typeFor lang _ = "listX of " <> typeFor lang (Proxy :: Proxy a)
type TestApi type TestApi
= "test" :> Header "header" [String] :> QueryFlag "flag" :> Get '[JSON] Int = "test" :> Header "header" [String] :> QueryFlag "flag" :> Get '[JSON] Int
@ -56,58 +59,57 @@ testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy TestApi)
listFromAPISpec :: Spec listFromAPISpec :: Spec
listFromAPISpec = describe "listFromAPI" $ do listFromAPISpec = describe "listFromAPI" $ do
it "generates 4 endpoints for TestApi" $ do it "generates 4 endpoints for TestApi" $ do
length testApi `shouldBe` 4 length testApi `shouldBe` 4
let [getReq, postReq, putReq, deleteReq] = testApi let [getReq, postReq, putReq, deleteReq] = testApi
it "collects all info for get request" $ do it "collects all info for get request" $ do
shouldBe getReq $ defReq shouldBe getReq $ defReq
{ _reqUrl = Url { _reqUrl = Url
[ Segment $ Static "test" ] [ Segment $ Static "test" ]
[ QueryArg ("flag", "boolX") Flag ] [ QueryArg ("flag", "boolX") Flag ]
, _reqMethod = "GET" , _reqMethod = "GET"
, _reqHeaders = [HeaderArg ("header", "listX of stringX")] , _reqHeaders = [HeaderArg ("header", "listX of stringX")]
, _reqBody = Nothing , _reqBody = Nothing
, _reqReturnType = "intX" , _reqReturnType = "intX"
, _funcName = ["get", "test"] , _reqFuncName = ["get", "test"]
} }
it "collects all info for post request" $ do it "collects all info for post request" $ do
shouldBe postReq $ defReq shouldBe postReq $ defReq
{ _reqUrl = Url { _reqUrl = Url
[ Segment $ Static "test" ] [ Segment $ Static "test" ]
[ QueryArg ("param", "intX") Normal ] [ QueryArg ("param", "intX") Normal ]
, _reqMethod = "POST" , _reqMethod = "POST"
, _reqHeaders = [] , _reqHeaders = []
, _reqBody = Just "listX of stringX" , _reqBody = Just "listX of stringX"
, _reqReturnType = "voidX" , _reqReturnType = "voidX"
, _funcName = ["post", "test"] , _reqFuncName = ["post", "test"]
} }
it "collects all info for put request" $ do it "collects all info for put request" $ do
shouldBe putReq $ defReq shouldBe putReq $ defReq
{ _reqUrl = Url { _reqUrl = Url
[ Segment $ Static "test" ] [ Segment $ Static "test" ]
-- Shoud this be |intX| or |listX of intX| ? -- Shoud this be |intX| or |listX of intX| ?
[ QueryArg ("params", "listX of intX") List ] [ QueryArg ("params", "listX of intX") List ]
, _reqMethod = "PUT" , _reqMethod = "PUT"
, _reqHeaders = [] , _reqHeaders = []
, _reqBody = Just "stringX" , _reqBody = Just "stringX"
, _reqReturnType = "voidX" , _reqReturnType = "voidX"
, _funcName = ["put", "test"] , _reqFuncName = ["put", "test"]
} }
it "collects all info for delete request" $ do
shouldBe deleteReq $ defReq
{ _reqUrl = Url
[ Segment $ Static "test"
, Segment $ Cap ("id", "intX") ]
[]
, _reqMethod = "DELETE"
, _reqHeaders = []
, _reqBody = Nothing
, _reqReturnType = "voidX"
, _funcName = ["delete", "test", "by", "id"]
}
it "collects all info for delete request" $ do
shouldBe deleteReq $ defReq
{ _reqUrl = Url
[ Segment $ Static "test"
, Segment $ Cap ("id", "intX") ]
[]
, _reqMethod = "DELETE"
, _reqHeaders = []
, _reqBody = Nothing
, _reqReturnType = "voidX"
, _reqFuncName = ["delete", "test", "by", "id"]
}

View file

@ -128,7 +128,7 @@ generateAngularJSWith ngOptions opts req = "\n" <>
fsep = if hasService then ":" else " =" fsep = if hasService then ":" else " ="
fname = namespace <> (functionNameBuilder opts $ req ^. funcName) fname = namespace <> (functionNameBuilder opts $ req ^. reqFuncName)
method = req ^. reqMethod method = req ^. reqMethod
url = if url' == "'" then "'/'" else url' url = if url' == "'" then "'/'" else url'

View file

@ -116,7 +116,7 @@ generateAxiosJSWith aopts opts req = "\n" <>
where where
hasNoModule = moduleName opts == "" hasNoModule = moduleName opts == ""
fname = namespace <> (functionNameBuilder opts $ req ^. funcName) fname = namespace <> (functionNameBuilder opts $ req ^. reqFuncName)
method = T.toLower . decodeUtf8 $ req ^. reqMethod method = T.toLower . decodeUtf8 $ req ^. reqMethod
url = if url' == "'" then "'/'" else url' url = if url' == "'" then "'/'" else url'

View file

@ -51,12 +51,19 @@ type JavaScriptGenerator = [Req] -> Text
-- customize the output -- customize the output
data CommonGeneratorOptions = CommonGeneratorOptions data CommonGeneratorOptions = CommonGeneratorOptions
{ {
functionNameBuilder :: FunctionName -> Text -- ^ function generating function names functionNameBuilder :: FunctionName -> Text
, requestBody :: Text -- ^ name used when a user want to send the request body (to let you redefine it) -- ^ function generating function names
, successCallback :: Text -- ^ name of the callback parameter when the request was successful , requestBody :: Text
, errorCallback :: Text -- ^ name of the callback parameter when the request reported an error -- ^ name used when a user want to send the request body
, moduleName :: Text -- ^ namespace on which we define the foreign function (empty mean local var) -- (to let you redefine it)
, urlPrefix :: Text -- ^ a prefix we should add to the Url in the codegen , successCallback :: Text
-- ^ name of the callback parameter when the request was successful
, errorCallback :: Text
-- ^ name of the callback parameter when the request reported an error
, moduleName :: Text
-- ^ namespace on which we define the foreign function (empty mean local var)
, urlPrefix :: Text
-- ^ a prefix we should add to the Url in the codegen
} }
-- | Default options. -- | Default options.

View file

@ -81,7 +81,7 @@ generateJQueryJSWith opts req = "\n" <>
namespace = if (moduleName opts) == "" namespace = if (moduleName opts) == ""
then "var " then "var "
else (moduleName opts) <> "." else (moduleName opts) <> "."
fname = namespace <> (functionNameBuilder opts $ req ^. funcName) fname = namespace <> (functionNameBuilder opts $ req ^. reqFuncName)
method = req ^. reqMethod method = req ^. reqMethod
url = if url' == "'" then "'/'" else url' url = if url' == "'" then "'/'" else url'

View file

@ -93,7 +93,7 @@ generateVanillaJSWith opts req = "\n" <>
namespace = if moduleName opts == "" namespace = if moduleName opts == ""
then "var " then "var "
else (moduleName opts) <> "." else (moduleName opts) <> "."
fname = namespace <> (functionNameBuilder opts $ req ^. funcName) fname = namespace <> (functionNameBuilder opts $ req ^. reqFuncName)
method = req ^. reqMethod method = req ^. reqMethod
url = if url' == "'" then "'/'" else url' url = if url' == "'" then "'/'" else url'

View file

@ -20,4 +20,4 @@ api :: Proxy API
api = Proxy api = Proxy
main :: IO () main :: IO ()
main = run 8080 (serve api EmptyConfig $ mock api Proxy) main = run 8080 (serve api $ mock api Proxy)

View file

@ -1,12 +1,12 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-} {-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
#include "overlapping-compat.h" #include "overlapping-compat.h"
@ -67,7 +67,6 @@ import Network.HTTP.Types.Status
import Network.Wai import Network.Wai
import Servant import Servant
import Servant.API.ContentTypes import Servant.API.ContentTypes
import Servant.Server.Internal.Config
import Test.QuickCheck.Arbitrary (Arbitrary (..), vector) import Test.QuickCheck.Arbitrary (Arbitrary (..), vector)
import Test.QuickCheck.Gen (Gen, generate) import Test.QuickCheck.Gen (Gen, generate)

View file

@ -52,7 +52,7 @@ spec = do
context "Get" $ do context "Get" $ do
let api :: Proxy (Get '[JSON] Body) let api :: Proxy (Get '[JSON] Body)
api = Proxy api = Proxy
app = serve api EmptyConfig (mock api Proxy) app = serve api (mock api Proxy)
with (return app) $ do with (return app) $ do
it "serves arbitrary response bodies" $ do it "serves arbitrary response bodies" $ do
get "/" `shouldRespondWith` 200{ get "/" `shouldRespondWith` 200{
@ -65,7 +65,7 @@ spec = do
withoutHeader :: Proxy (Get '[JSON] (Headers '[] Body)) withoutHeader :: Proxy (Get '[JSON] (Headers '[] Body))
withoutHeader = Proxy withoutHeader = Proxy
toApp :: (HasMock api '[]) => Proxy api -> IO Application toApp :: (HasMock api '[]) => Proxy api -> IO Application
toApp api = return $ serve api EmptyConfig (mock api (Proxy :: Proxy '[])) toApp api = return $ serve api (mock api (Proxy :: Proxy '[]))
with (toApp withHeader) $ do with (toApp withHeader) $ do
it "serves arbitrary response bodies" $ do it "serves arbitrary response bodies" $ do
get "/" `shouldRespondWith` 200{ get "/" `shouldRespondWith` 200{

View file

@ -59,7 +59,7 @@ server = helloH :<|> postGreetH :<|> deleteGreetH
-- Turn the server into a WAI app. 'serve' is provided by servant, -- Turn the server into a WAI app. 'serve' is provided by servant,
-- more precisely by the Servant.Server module. -- more precisely by the Servant.Server module.
test :: Application test :: Application
test = serve testApi EmptyConfig server test = serve testApi server
-- Run the server. -- Run the server.
-- --

View file

@ -1,4 +1,5 @@
{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
@ -8,6 +9,7 @@
module Servant.Server module Servant.Server
( -- * Run a wai application from an API ( -- * Run a wai application from an API
serve serve
, serveWithConfig
, -- * Construct a wai Application from an API , -- * Construct a wai Application from an API
toApplication toApplication
@ -104,18 +106,18 @@ import Servant.Server.Internal.Enter
-- > myApi :: Proxy MyApi -- > myApi :: Proxy MyApi
-- > myApi = Proxy -- > myApi = Proxy
-- > -- >
-- > config :: Config '[]
-- > config = EmptyConfig
-- >
-- > app :: Application -- > app :: Application
-- > app = serve myApi config server -- > app = serve myApi server
-- > -- >
-- > main :: IO () -- > main :: IO ()
-- > main = Network.Wai.Handler.Warp.run 8080 app -- > main = Network.Wai.Handler.Warp.run 8080 app
-- --
serve :: (HasServer layout config) serve :: (HasServer layout '[]) => Proxy layout -> Server layout -> Application
serve p = serveWithConfig p EmptyConfig
serveWithConfig :: (HasServer layout config)
=> Proxy layout -> Config config -> Server layout -> Application => Proxy layout -> Config config -> Server layout -> Application
serve p config server = toApplication (runRouter (route p config d)) serveWithConfig p config server = toApplication (runRouter (route p config d))
where where
d = Delayed r r r (\ _ _ -> Route server) d = Delayed r r r (\ _ _ -> Route server)
r = return (Route ()) r = return (Route ())

View file

@ -158,7 +158,7 @@ methodCheck method request
acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> IO (RouteResult ()) acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> IO (RouteResult ())
acceptCheck proxy accH acceptCheck proxy accH
| canHandleAcceptH proxy (AcceptHeader accH) = return $ Route () | canHandleAcceptH proxy (AcceptHeader accH) = return $ Route ()
| otherwise = return $ Fail err406 | otherwise = return $ FailFatal err406
methodRouter :: (AllCTRender ctypes a) methodRouter :: (AllCTRender ctypes a)
=> Method -> Proxy ctypes -> Status => Method -> Proxy ctypes -> Status

View file

@ -11,14 +11,8 @@ module Servant.Server.Internal.RoutingApplication where
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
#endif #endif
import Control.Monad.Trans.Except (ExceptT, runExceptT) import Control.Monad.Trans.Except (ExceptT, runExceptT)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Data.IORef (newIORef, readIORef,
writeIORef)
import Network.Wai (Application, Request, import Network.Wai (Application, Request,
Response, ResponseReceived, Response, ResponseReceived)
requestBody,
strictRequestBody)
import Servant.Server.Internal.ServantErr import Servant.Server.Internal.ServantErr
type RoutingApplication = type RoutingApplication =
@ -33,34 +27,8 @@ data RouteResult a =
| Route !a | Route !a
deriving (Eq, Show, Read, Functor) deriving (Eq, Show, Read, Functor)
data ReqBodyState = Uncalled
| Called !B.ByteString
| Done !B.ByteString
toApplication :: RoutingApplication -> Application toApplication :: RoutingApplication -> Application
toApplication ra request respond = do toApplication ra request respond = ra request routingRespond
reqBodyRef <- newIORef Uncalled
-- We may need to consume the requestBody more than once. In order to
-- maintain the illusion that 'requestBody' works as expected,
-- 'ReqBodyState' is introduced, and the complete body is memoized and
-- returned as many times as requested with empty "Done" marker chunks in
-- between.
-- See https://github.com/haskell-servant/servant/issues/3
let memoReqBody = do
ior <- readIORef reqBodyRef
case ior of
Uncalled -> do
r <- BL.toStrict <$> strictRequestBody request
writeIORef reqBodyRef $ Done r
return r
Called bs -> do
writeIORef reqBodyRef $ Done bs
return bs
Done bs -> do
writeIORef reqBodyRef $ Called bs
return B.empty
ra request{ requestBody = memoReqBody } routingRespond
where where
routingRespond :: RouteResult Response -> IO ResponseReceived routingRespond :: RouteResult Response -> IO ResponseReceived
routingRespond (Fail err) = respond $ responseServantErr err routingRespond (Fail err) = respond $ responseServantErr err
@ -98,10 +66,10 @@ toApplication ra request respond = do
-- --
-- There are two reasons: -- There are two reasons:
-- --
-- 1. Currently, the order in which we perform checks coincides -- 1. In a straight-forward implementation, the order in which we
-- with the error we will generate. This is because during checks, -- perform checks will determine the error we generate. This is
-- once an error occurs, we do not perform any subsequent checks, -- because once an error occurs, we would abort and not perform
-- but rather return this error. -- any subsequent checks, but rather return the current error.
-- --
-- This is not a necessity: we could continue doing other checks, -- This is not a necessity: we could continue doing other checks,
-- and choose the preferred error. However, that would in general -- and choose the preferred error. However, that would in general
@ -159,7 +127,7 @@ data Delayed :: * -> * where
-> Delayed c -> Delayed c
instance Functor Delayed where instance Functor Delayed where
fmap f (Delayed a b c g) = Delayed a b c ((fmap.fmap.fmap) f g) fmap f (Delayed a b c g) = Delayed a b c ((fmap . fmap . fmap) f g)
-- | Add a capture to the end of the capture block. -- | Add a capture to the end of the capture block.
addCapture :: Delayed (a -> b) addCapture :: Delayed (a -> b)
@ -240,9 +208,9 @@ runAction :: Delayed (ExceptT ServantErr IO a)
-> IO r -> IO r
runAction action respond k = runDelayed action >>= go >>= respond runAction action respond k = runDelayed action >>= go >>= respond
where where
go (Fail e) = return $ Fail e go (Fail e) = return $ Fail e
go (FailFatal e) = return $ FailFatal e go (FailFatal e) = return $ FailFatal e
go (Route a) = do go (Route a) = do
e <- runExceptT a e <- runExceptT a
case e of case e of
Left err -> return . Route $ responseServantErr err Left err -> return . Route $ responseServantErr err

View file

@ -42,7 +42,7 @@ errorOrderServer = \_ _ -> throwE err402
errorOrderSpec :: Spec errorOrderSpec :: Spec
errorOrderSpec = describe "HTTP error order" errorOrderSpec = describe "HTTP error order"
$ with (return $ serve errorOrderApi EmptyConfig errorOrderServer) $ do $ with (return $ serve errorOrderApi errorOrderServer) $ do
let badContentType = (hContentType, "text/plain") let badContentType = (hContentType, "text/plain")
badAccept = (hAccept, "text/plain") badAccept = (hAccept, "text/plain")
badMethod = methodGet badMethod = methodGet
@ -89,7 +89,7 @@ prioErrorsApi = Proxy
prioErrorsSpec :: Spec prioErrorsSpec :: Spec
prioErrorsSpec = describe "PrioErrors" $ do prioErrorsSpec = describe "PrioErrors" $ do
let server = return let server = return
with (return $ serve prioErrorsApi EmptyConfig server) $ do with (return $ serve prioErrorsApi server) $ do
let check (mdescr, method) path (cdescr, ctype, body) resp = let check (mdescr, method) path (cdescr, ctype, body) resp =
it fulldescr $ it fulldescr $
Test.Hspec.Wai.request method path [(hContentType, ctype)] body Test.Hspec.Wai.request method path [(hContentType, ctype)] body
@ -154,7 +154,7 @@ errorRetryServer
errorRetrySpec :: Spec errorRetrySpec :: Spec
errorRetrySpec = describe "Handler search" errorRetrySpec = describe "Handler search"
$ with (return $ serve errorRetryApi EmptyConfig errorRetryServer) $ do $ with (return $ serve errorRetryApi errorRetryServer) $ do
let jsonCT = (hContentType, "application/json") let jsonCT = (hContentType, "application/json")
jsonAccept = (hAccept, "application/json") jsonAccept = (hAccept, "application/json")
@ -168,6 +168,10 @@ errorRetrySpec = describe "Handler search"
request methodGet "a" [jsonCT, jsonAccept] jsonBody request methodGet "a" [jsonCT, jsonAccept] jsonBody
`shouldRespondWith` 200 { matchBody = Just $ encode (4 :: Int) } `shouldRespondWith` 200 { matchBody = Just $ encode (4 :: Int) }
it "should not continue when body cannot be decoded" $ do
request methodPost "a" [jsonCT, jsonAccept] "a string"
`shouldRespondWith` 400
-- }}} -- }}}
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
-- * Error Choice {{{ -- * Error Choice {{{
@ -194,7 +198,7 @@ errorChoiceServer = return 0
errorChoiceSpec :: Spec errorChoiceSpec :: Spec
errorChoiceSpec = describe "Multiple handlers return errors" errorChoiceSpec = describe "Multiple handlers return errors"
$ with (return $ serve errorChoiceApi EmptyConfig errorChoiceServer) $ do $ with (return $ serve errorChoiceApi errorChoiceServer) $ do
it "should respond with 404 if no path matches" $ do it "should respond with 404 if no path matches" $ do
request methodGet "" [] "" `shouldRespondWith` 404 request methodGet "" [] "" `shouldRespondWith` 404

View file

@ -48,12 +48,12 @@ combinedReaderServer = enter fReader combinedReaderServer'
enterSpec :: Spec enterSpec :: Spec
enterSpec = describe "Enter" $ do enterSpec = describe "Enter" $ do
with (return (serve readerAPI EmptyConfig readerServer)) $ do with (return (serve readerAPI readerServer)) $ do
it "allows running arbitrary monads" $ do it "allows running arbitrary monads" $ do
get "int" `shouldRespondWith` "1797" get "int" `shouldRespondWith` "1797"
post "string" "3" `shouldRespondWith` "\"hi\""{ matchStatus = 200 } post "string" "3" `shouldRespondWith` "\"hi\""{ matchStatus = 200 }
with (return (serve combinedAPI EmptyConfig combinedReaderServer)) $ do with (return (serve combinedAPI combinedReaderServer)) $ do
it "allows combnation of enters" $ do it "allows combnation of enters" $ do
get "bool" `shouldRespondWith` "true" get "bool" `shouldRespondWith` "true"

View file

@ -30,7 +30,7 @@ testServer s = return s
oneEntryApp :: Application oneEntryApp :: Application
oneEntryApp = oneEntryApp =
serve (Proxy :: Proxy OneEntryAPI) config testServer serveWithConfig (Proxy :: Proxy OneEntryAPI) config testServer
where where
config :: Config '[String] config :: Config '[String]
config = "configEntry" :. EmptyConfig config = "configEntry" :. EmptyConfig
@ -40,7 +40,7 @@ type OneEntryTwiceAPI =
"bar" :> ExtractFromConfig :> Get '[JSON] String "bar" :> ExtractFromConfig :> Get '[JSON] String
oneEntryTwiceApp :: Application oneEntryTwiceApp :: Application
oneEntryTwiceApp = serve (Proxy :: Proxy OneEntryTwiceAPI) config $ oneEntryTwiceApp = serveWithConfig (Proxy :: Proxy OneEntryTwiceAPI) config $
testServer :<|> testServer :<|>
testServer testServer
where where
@ -68,7 +68,7 @@ type InjectAPI =
Get '[JSON] String Get '[JSON] String
injectApp :: Application injectApp :: Application
injectApp = serve (Proxy :: Proxy InjectAPI) config $ injectApp = serveWithConfig (Proxy :: Proxy InjectAPI) config $
(\ s -> return s) :<|> (\ s -> return s) :<|>
(\ s -> return ("tagged: " ++ s)) (\ s -> return ("tagged: " ++ s))
where where
@ -90,7 +90,7 @@ type WithBirdfaceAPI =
"bar" :> ExtractFromConfig :> Get '[JSON] String "bar" :> ExtractFromConfig :> Get '[JSON] String
withBirdfaceApp :: Application withBirdfaceApp :: Application
withBirdfaceApp = serve (Proxy :: Proxy WithBirdfaceAPI) config $ withBirdfaceApp = serveWithConfig (Proxy :: Proxy WithBirdfaceAPI) config $
testServer :<|> testServer :<|>
testServer testServer
where where
@ -112,7 +112,7 @@ type NamedConfigAPI =
ExtractFromConfig :> Get '[JSON] String) ExtractFromConfig :> Get '[JSON] String)
namedConfigApp :: Application namedConfigApp :: Application
namedConfigApp = serve (Proxy :: Proxy NamedConfigAPI) config return namedConfigApp = serveWithConfig (Proxy :: Proxy NamedConfigAPI) config return
where where
config :: Config '[NamedConfig "sub" '[String]] config :: Config '[NamedConfig "sub" '[String]]
config = NamedConfig ("descend" :. EmptyConfig) :. EmptyConfig config = NamedConfig ("descend" :. EmptyConfig) :. EmptyConfig

View file

@ -49,7 +49,7 @@ import Servant.API ((:<|>) (..), (:>), Capture, Delete,
StdMethod (..), Verb, addHeader) StdMethod (..), Verb, addHeader)
import Servant.API.Internal.Test.ComprehensiveAPI import Servant.API.Internal.Test.ComprehensiveAPI
import Servant.Server (ServantErr (..), Server, err404, import Servant.Server (ServantErr (..), Server, err404,
serve, Config(EmptyConfig)) serve, serveWithConfig, Config(EmptyConfig))
import Test.Hspec (Spec, context, describe, it, import Test.Hspec (Spec, context, describe, it,
shouldBe, shouldContain) shouldBe, shouldContain)
import Test.Hspec.Wai (get, liftIO, matchHeaders, import Test.Hspec.Wai (get, liftIO, matchHeaders,
@ -67,7 +67,7 @@ import Servant.Server.Internal.Config
-- * comprehensive api test -- * comprehensive api test
-- This declaration simply checks that all instances are in place. -- This declaration simply checks that all instances are in place.
_ = serve comprehensiveAPI comprehensiveApiConfig _ = serveWithConfig comprehensiveAPI comprehensiveApiConfig
comprehensiveApiConfig :: Config '[NamedConfig "foo" '[]] comprehensiveApiConfig :: Config '[NamedConfig "foo" '[]]
comprehensiveApiConfig = NamedConfig EmptyConfig :. EmptyConfig comprehensiveApiConfig = NamedConfig EmptyConfig :. EmptyConfig
@ -112,7 +112,7 @@ verbSpec = describe "Servant.API.Verb" $ do
wrongMethod m = if m == methodPatch then methodPost else methodPatch wrongMethod m = if m == methodPatch then methodPost else methodPatch
test desc api method (status :: Int) = context desc $ test desc api method (status :: Int) = context desc $
with (return $ serve api EmptyConfig server) $ do with (return $ serve api server) $ do
-- HEAD and 214/215 need not return bodies -- HEAD and 214/215 need not return bodies
unless (status `elem` [214, 215] || method == methodHead) $ unless (status `elem` [214, 215] || method == methodHead) $
@ -187,7 +187,7 @@ captureServer legs = case legs of
captureSpec :: Spec captureSpec :: Spec
captureSpec = do captureSpec = do
describe "Servant.API.Capture" $ do describe "Servant.API.Capture" $ do
with (return (serve captureApi EmptyConfig captureServer)) $ do with (return (serve captureApi captureServer)) $ do
it "can capture parts of the 'pathInfo'" $ do it "can capture parts of the 'pathInfo'" $ do
response <- get "/2" response <- get "/2"
@ -198,7 +198,6 @@ captureSpec = do
with (return (serve with (return (serve
(Proxy :: Proxy (Capture "captured" String :> Raw)) (Proxy :: Proxy (Capture "captured" String :> Raw))
EmptyConfig
(\ "captured" request_ respond -> (\ "captured" request_ respond ->
respond $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do respond $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do
it "strips the captured path snippet from pathInfo" $ do it "strips the captured path snippet from pathInfo" $ do
@ -232,7 +231,7 @@ queryParamSpec :: Spec
queryParamSpec = do queryParamSpec = do
describe "Servant.API.QueryParam" $ do describe "Servant.API.QueryParam" $ do
it "allows retrieving simple GET parameters" $ it "allows retrieving simple GET parameters" $
(flip runSession) (serve queryParamApi EmptyConfig qpServer) $ do (flip runSession) (serve queryParamApi qpServer) $ do
let params1 = "?name=bob" let params1 = "?name=bob"
response1 <- Network.Wai.Test.request defaultRequest{ response1 <- Network.Wai.Test.request defaultRequest{
rawQueryString = params1, rawQueryString = params1,
@ -244,7 +243,7 @@ queryParamSpec = do
} }
it "allows retrieving lists in GET parameters" $ it "allows retrieving lists in GET parameters" $
(flip runSession) (serve queryParamApi EmptyConfig qpServer) $ do (flip runSession) (serve queryParamApi qpServer) $ do
let params2 = "?names[]=bob&names[]=john" let params2 = "?names[]=bob&names[]=john"
response2 <- Network.Wai.Test.request defaultRequest{ response2 <- Network.Wai.Test.request defaultRequest{
rawQueryString = params2, rawQueryString = params2,
@ -258,7 +257,7 @@ queryParamSpec = do
it "allows retrieving value-less GET parameters" $ it "allows retrieving value-less GET parameters" $
(flip runSession) (serve queryParamApi EmptyConfig qpServer) $ do (flip runSession) (serve queryParamApi qpServer) $ do
let params3 = "?capitalize" let params3 = "?capitalize"
response3 <- Network.Wai.Test.request defaultRequest{ response3 <- Network.Wai.Test.request defaultRequest{
rawQueryString = params3, rawQueryString = params3,
@ -310,7 +309,7 @@ reqBodySpec = describe "Servant.API.ReqBody" $ do
mkReq method x = Test.Hspec.Wai.request method x mkReq method x = Test.Hspec.Wai.request method x
[(hContentType, "application/json;charset=utf-8")] [(hContentType, "application/json;charset=utf-8")]
with (return $ serve reqBodyApi EmptyConfig server) $ do with (return $ serve reqBodyApi server) $ do
it "passes the argument to the handler" $ do it "passes the argument to the handler" $ do
response <- mkReq methodPost "" (encode alice) response <- mkReq methodPost "" (encode alice)
@ -343,13 +342,13 @@ headerSpec = describe "Servant.API.Header" $ do
expectsString (Just x) = when (x /= "more from you") $ error "Expected more from you" expectsString (Just x) = when (x /= "more from you") $ error "Expected more from you"
expectsString Nothing = error "Expected a string" expectsString Nothing = error "Expected a string"
with (return (serve headerApi EmptyConfig expectsInt)) $ do with (return (serve headerApi expectsInt)) $ do
let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader", "5")] let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader", "5")]
it "passes the header to the handler (Int)" $ it "passes the header to the handler (Int)" $
delete' "/" "" `shouldRespondWith` 200 delete' "/" "" `shouldRespondWith` 200
with (return (serve headerApi EmptyConfig expectsString)) $ do with (return (serve headerApi expectsString)) $ do
let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader", "more from you")] let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader", "more from you")]
it "passes the header to the handler (String)" $ it "passes the header to the handler (String)" $
@ -373,7 +372,7 @@ rawSpec :: Spec
rawSpec = do rawSpec = do
describe "Servant.API.Raw" $ do describe "Servant.API.Raw" $ do
it "runs applications" $ do it "runs applications" $ do
(flip runSession) (serve rawApi EmptyConfig (rawApplication (const (42 :: Integer)))) $ do (flip runSession) (serve rawApi (rawApplication (const (42 :: Integer)))) $ do
response <- Network.Wai.Test.request defaultRequest{ response <- Network.Wai.Test.request defaultRequest{
pathInfo = ["foo"] pathInfo = ["foo"]
} }
@ -381,7 +380,7 @@ rawSpec = do
simpleBody response `shouldBe` "42" simpleBody response `shouldBe` "42"
it "gets the pathInfo modified" $ do it "gets the pathInfo modified" $ do
(flip runSession) (serve rawApi EmptyConfig (rawApplication pathInfo)) $ do (flip runSession) (serve rawApi (rawApplication pathInfo)) $ do
response <- Network.Wai.Test.request defaultRequest{ response <- Network.Wai.Test.request defaultRequest{
pathInfo = ["foo", "bar"] pathInfo = ["foo", "bar"]
} }
@ -415,7 +414,7 @@ alternativeServer =
alternativeSpec :: Spec alternativeSpec :: Spec
alternativeSpec = do alternativeSpec = do
describe "Servant.API.Alternative" $ do describe "Servant.API.Alternative" $ do
with (return $ serve alternativeApi EmptyConfig alternativeServer) $ do with (return $ serve alternativeApi alternativeServer) $ do
it "unions endpoints" $ do it "unions endpoints" $ do
response <- get "/foo" response <- get "/foo"
@ -450,7 +449,7 @@ responseHeadersServer = let h = return $ addHeader 5 $ addHeader "kilroy" "hi"
responseHeadersSpec :: Spec responseHeadersSpec :: Spec
responseHeadersSpec = describe "ResponseHeaders" $ do responseHeadersSpec = describe "ResponseHeaders" $ do
with (return $ serve (Proxy :: Proxy ResponseHeadersApi) EmptyConfig responseHeadersServer) $ do with (return $ serve (Proxy :: Proxy ResponseHeadersApi) responseHeadersServer) $ do
let methods = [methodGet, methodPost, methodPut, methodPatch] let methods = [methodGet, methodPost, methodPut, methodPatch]
@ -516,7 +515,7 @@ miscServ = versionHandler
hostHandler = return . show hostHandler = return . show
miscCombinatorSpec :: Spec miscCombinatorSpec :: Spec
miscCombinatorSpec = with (return $ serve miscApi EmptyConfig miscServ) $ miscCombinatorSpec = with (return $ serve miscApi miscServ) $
describe "Misc. combinators for request inspection" $ do describe "Misc. combinators for request inspection" $ do
it "Successfully gets the HTTP version specified in the request" $ it "Successfully gets the HTTP version specified in the request" $
go "/version" "\"HTTP/1.0\"" go "/version" "\"HTTP/1.0\""

View file

@ -16,7 +16,7 @@ import Test.Hspec (Spec, around_, describe, it)
import Test.Hspec.Wai (get, shouldRespondWith, with) import Test.Hspec.Wai (get, shouldRespondWith, with)
import Servant.API ((:<|>) ((:<|>)), Capture, Get, Raw, (:>), JSON) import Servant.API ((:<|>) ((:<|>)), Capture, Get, Raw, (:>), JSON)
import Servant.Server (Server, serve, Config(EmptyConfig)) import Servant.Server (Server, serve)
import Servant.ServerSpec (Person (Person)) import Servant.ServerSpec (Person (Person))
import Servant.Utils.StaticFiles (serveDirectory) import Servant.Utils.StaticFiles (serveDirectory)
@ -29,7 +29,7 @@ api :: Proxy Api
api = Proxy api = Proxy
app :: Application app :: Application
app = serve api EmptyConfig server app = serve api server
server :: Server Api server :: Server Api
server = server =

View file

@ -34,15 +34,15 @@ data Verb (method :: k1) (statusCode :: Nat) (contentTypes :: [*]) a
-- the relevant information is summarily presented here. -- the relevant information is summarily presented here.
-- | 'GET' with 200 status code. -- | 'GET' with 200 status code.
type Get contentTypes a = Verb 'GET 200 contentTypes a type Get = Verb 'GET 200
-- | 'POST' with 200 status code. -- | 'POST' with 200 status code.
type Post contentTypes a = Verb 'POST 200 contentTypes a type Post = Verb 'POST 200
-- | 'PUT' with 200 status code. -- | 'PUT' with 200 status code.
type Put contentTypes a = Verb 'PUT 200 contentTypes a type Put = Verb 'PUT 200
-- | 'DELETE' with 200 status code. -- | 'DELETE' with 200 status code.
type Delete contentTypes a = Verb 'DELETE 200 contentTypes a type Delete = Verb 'DELETE 200
-- | 'PATCH' with 200 status code. -- | 'PATCH' with 200 status code.
type Patch contentTypes a = Verb 'PATCH 200 contentTypes a type Patch = Verb 'PATCH 200
-- * Other responses -- * Other responses
@ -58,7 +58,7 @@ type Patch contentTypes a = Verb 'PATCH 200 contentTypes a
-- | 'POST' with 201 status code. -- | 'POST' with 201 status code.
-- --
type PostCreated contentTypes a = Verb 'POST 201 contentTypes a type PostCreated = Verb 'POST 201
-- ** 202 Accepted -- ** 202 Accepted
@ -69,15 +69,15 @@ type PostCreated contentTypes a = Verb 'POST 201 contentTypes a
-- estimate of when the processing will be finished. -- estimate of when the processing will be finished.
-- | 'GET' with 202 status code. -- | 'GET' with 202 status code.
type GetAccepted contentTypes a = Verb 'GET 202 contentTypes a type GetAccepted = Verb 'GET 202
-- | 'POST' with 202 status code. -- | 'POST' with 202 status code.
type PostAccepted contentTypes a = Verb 'POST 202 contentTypes a type PostAccepted = Verb 'POST 202
-- | 'DELETE' with 202 status code. -- | 'DELETE' with 202 status code.
type DeleteAccepted contentTypes a = Verb 'DELETE 202 contentTypes a type DeleteAccepted = Verb 'DELETE 202
-- | 'PATCH' with 202 status code. -- | 'PATCH' with 202 status code.
type PatchAccepted contentTypes a = Verb 'PATCH 202 contentTypes a type PatchAccepted = Verb 'PATCH 202
-- | 'PUT' with 202 status code. -- | 'PUT' with 202 status code.
type PutAccepted contentTypes a = Verb 'PUT 202 contentTypes a type PutAccepted = Verb 'PUT 202
-- ** 203 Non-Authoritative Information -- ** 203 Non-Authoritative Information
@ -86,15 +86,15 @@ type PutAccepted contentTypes a = Verb 'PUT 202 contentTypes a
-- information may come from a third-party. -- information may come from a third-party.
-- | 'GET' with 203 status code. -- | 'GET' with 203 status code.
type GetNonAuthoritative contentTypes a = Verb 'GET 203 contentTypes a type GetNonAuthoritative = Verb 'GET 203
-- | 'POST' with 203 status code. -- | 'POST' with 203 status code.
type PostNonAuthoritative contentTypes a = Verb 'POST 203 contentTypes a type PostNonAuthoritative = Verb 'POST 203
-- | 'DELETE' with 203 status code. -- | 'DELETE' with 203 status code.
type DeleteNonAuthoritative contentTypes a = Verb 'DELETE 203 contentTypes a type DeleteNonAuthoritative = Verb 'DELETE 203
-- | 'PATCH' with 203 status code. -- | 'PATCH' with 203 status code.
type PatchNonAuthoritative contentTypes a = Verb 'PATCH 203 contentTypes a type PatchNonAuthoritative = Verb 'PATCH 203
-- | 'PUT' with 203 status code. -- | 'PUT' with 203 status code.
type PutNonAuthoritative contentTypes a = Verb 'PUT 203 contentTypes a type PutNonAuthoritative = Verb 'PUT 203
-- ** 204 No Content -- ** 204 No Content
@ -105,15 +105,15 @@ type PutNonAuthoritative contentTypes a = Verb 'PUT 203 contentTypes a
-- If the document view should be reset, use @205 Reset Content@. -- If the document view should be reset, use @205 Reset Content@.
-- | 'GET' with 204 status code. -- | 'GET' with 204 status code.
type GetNoContent contentTypes noContent = Verb 'GET 204 contentTypes noContent type GetNoContent = Verb 'GET 204
-- | 'POST' with 204 status code. -- | 'POST' with 204 status code.
type PostNoContent contentTypes noContent = Verb 'POST 204 contentTypes noContent type PostNoContent = Verb 'POST 204
-- | 'DELETE' with 204 status code. -- | 'DELETE' with 204 status code.
type DeleteNoContent contentTypes noContent = Verb 'DELETE 204 contentTypes noContent type DeleteNoContent = Verb 'DELETE 204
-- | 'PATCH' with 204 status code. -- | 'PATCH' with 204 status code.
type PatchNoContent contentTypes noContent = Verb 'PATCH 204 contentTypes noContent type PatchNoContent = Verb 'PATCH 204
-- | 'PUT' with 204 status code. -- | 'PUT' with 204 status code.
type PutNoContent contentTypes noContent = Verb 'PUT 204 contentTypes noContent type PutNoContent = Verb 'PUT 204
-- ** 205 Reset Content -- ** 205 Reset Content
@ -124,15 +124,15 @@ type PutNoContent contentTypes noContent = Verb 'PUT 204 contentTypes noContent
-- If the document view should not be reset, use @204 No Content@. -- If the document view should not be reset, use @204 No Content@.
-- | 'GET' with 205 status code. -- | 'GET' with 205 status code.
type GetResetContent contentTypes noContent = Verb 'GET 205 contentTypes noContent type GetResetContent = Verb 'GET 205
-- | 'POST' with 205 status code. -- | 'POST' with 205 status code.
type PostResetContent contentTypes noContent = Verb 'POST 205 contentTypes noContent type PostResetContent = Verb 'POST 205
-- | 'DELETE' with 205 status code. -- | 'DELETE' with 205 status code.
type DeleteResetContent contentTypes noContent = Verb 'DELETE 205 contentTypes noContent type DeleteResetContent = Verb 'DELETE 205
-- | 'PATCH' with 205 status code. -- | 'PATCH' with 205 status code.
type PatchResetContent contentTypes noContent = Verb 'PATCH 205 contentTypes noContent type PatchResetContent = Verb 'PATCH 205
-- | 'PUT' with 205 status code. -- | 'PUT' with 205 status code.
type PutResetContent contentTypes noContent = Verb 'PUT 205 contentTypes noContent type PutResetContent = Verb 'PUT 205
-- ** 206 Partial Content -- ** 206 Partial Content
@ -144,7 +144,7 @@ type PutResetContent contentTypes noContent = Verb 'PUT 205 contentTypes noConte
-- RFC7233 Section 4.1> -- RFC7233 Section 4.1>
-- | 'GET' with 206 status code. -- | 'GET' with 206 status code.
type GetPartialContent contentTypes noContent = Verb 'GET 206 contentTypes noContent type GetPartialContent = Verb 'GET 206
class ReflectMethod a where class ReflectMethod a where