packages feed

servant-server 0.18.3 → 0.19

raw patch · 10 files changed

+190/−96 lines, 10 filesdep +constraintsdep ~aesondep ~servantdep ~textnew-uploader

Dependencies added: constraints

Dependency ranges changed: aeson, servant, text

Files

CHANGELOG.md view
@@ -1,6 +1,24 @@ [The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md) [Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md) +Package versions follow the [Package Versioning Policy](https://pvp.haskell.org/): in A.B.C, bumps to either A or B represent major versions.++0.19+----++### Significant changes++- Drop support for GHC < 8.6.+- Support GHC 9.0 (GHC 9.2 should work as well, but isn't fully tested yet).+- Support Aeson 2 ([#1475](https://github.com/haskell-servant/servant/pull/1475)),+  which fixes a [DOS vulnerability](https://github.com/haskell/aeson/issues/864)+  related to hash collisions.+- Add `NamedRoutes` combinator, making support for records first-class in Servant+  ([#1388](https://github.com/haskell-servant/servant/pull/1388)).+- Add custom type errors for partially applied combinators+  ([#1289](https://github.com/haskell-servant/servant/pull/1289),+  [#1486](https://github.com/haskell-servant/servant/pull/1486)).+ 0.18.3 ------ 
example/greet.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds         #-} {-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds         #-} {-# LANGUAGE TypeFamilies      #-}@@ -16,6 +17,8 @@ import           Network.Wai.Handler.Warp  import           Servant+import           Servant.Server.Generic ()+import           Servant.API.Generic  -- * Example @@ -38,6 +41,14 @@        -- DELETE /greet/:greetid   :<|> "greet" :> Capture "greetid" Text :> Delete '[JSON] NoContent +  :<|> NamedRoutes OtherRoutes++data OtherRoutes mode = OtherRoutes+  { version :: mode :- Get '[JSON] Int+  , bye :: mode :- "bye" :> Capture "name" Text :> Get '[JSON] Text+  }+  deriving Generic+ testApi :: Proxy TestApi testApi = Proxy @@ -48,9 +59,13 @@ -- -- Each handler runs in the 'Handler' monad. server :: Server TestApi-server = helloH :<|> postGreetH :<|> deleteGreetH+server = helloH :<|> postGreetH :<|> deleteGreetH :<|> otherRoutes+  where otherRoutes = OtherRoutes {..} -  where helloH name Nothing = helloH name (Just False)+        bye name = pure $ "Bye, " <> name <> " !"+        version = pure 42++        helloH name Nothing = helloH name (Just False)         helloH name (Just False) = return . Greet $ "Hello, " <> name         helloH name (Just True) = return . Greet . toUpper $ "Hello, " <> name 
servant-server.cabal view
@@ -1,6 +1,6 @@-cabal-version:       >=1.10+cabal-version:       2.2 name:                servant-server-version:             0.18.3+version:             0.19  synopsis:            A family of combinators for defining webservices APIs and serving them category:            Servant, Web@@ -17,13 +17,13 @@  homepage:            http://docs.servant.dev/ bug-reports:         http://github.com/haskell-servant/servant/issues-license:             BSD3+license:             BSD-3-Clause license-file:        LICENSE author:              Servant Contributors maintainer:          haskell-servant-maintainers@googlegroups.com copyright:           2014-2016 Zalora South East Asia Pte Ltd, 2016-2019 Servant Contributors build-type:          Simple-tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.2 || ==9.0.1+tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.2 || ==9.0.1  extra-source-files:   CHANGELOG.md@@ -62,6 +62,7 @@   build-depends:       base                >= 4.9      && < 4.16     , bytestring          >= 0.10.8.1 && < 0.12+    , constraints         >= 0.2      && < 0.14     , containers          >= 0.5.7.1  && < 0.7     , mtl                 >= 2.2.2    && < 2.3     , text                >= 1.2.3.0  && < 1.3@@ -71,7 +72,7 @@   -- Servant dependencies   -- strict dependency as we re-export 'servant' things.   build-depends:-      servant             >= 0.18.3   && < 0.18.4+      servant             >= 0.19     , http-api-data       >= 0.4.1    && < 0.4.4    -- Other dependencies: Lower bound around what is in the latest Stackage LTS.@@ -113,7 +114,7 @@     , text    build-depends:-      aeson               >= 1.4.1.0  && < 1.6+      aeson               >= 1.4.1.0  && < 3     , warp                >= 3.2.25   && < 3.4  test-suite spec@@ -156,7 +157,7 @@    -- Additional dependencies   build-depends:-      aeson                >= 1.4.1.0  && < 1.6+      aeson                >= 1.4.1.0  && < 3     , directory            >= 1.3.0.0  && < 1.4     , hspec                >= 2.6.0    && < 2.9     , hspec-wai            >= 0.10.1   && < 0.12
src/Servant/Server.hs view
@@ -1,9 +1,10 @@-{-# LANGUAGE ConstraintKinds  #-}-{-# LANGUAGE DataKinds        #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE RankNTypes       #-}-{-# LANGUAGE TypeFamilies     #-}-{-# LANGUAGE TypeOperators    #-}+{-# LANGUAGE ConstraintKinds     #-}+{-# LANGUAGE DataKinds           #-}+{-# LANGUAGE FlexibleContexts    #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies        #-}+{-# LANGUAGE TypeOperators       #-}  -- | This module lets you implement 'Server's for defined APIs. You'll -- most likely just need 'serve'.@@ -11,6 +12,8 @@   ( -- * Run a wai application from an API     serve   , serveWithContext+  , serveWithContextT+  , ServerContext    , -- * Construct a wai Application from an API     toApplication@@ -128,6 +131,15 @@  -- * Implementing Servers +-- | Constraints that need to be satisfied on a context for it to be passed to 'serveWithContext'.+--+-- Typically, this will add default context entries to the context. You shouldn't typically+-- need to worry about these constraints, but if you write a helper function that wraps+-- 'serveWithContext', you might need to include this constraint.+type ServerContext context =+  ( HasContextEntry (context .++ DefaultErrorFormatters) ErrorFormatters+  )+ -- | 'serve' allows you to implement an API and produce a wai 'Application'. -- -- Example:@@ -157,11 +169,21 @@ -- 'defaultErrorFormatters' will always be appended to the end of the passed context, -- but if you pass your own formatter, it will override the default one. serveWithContext :: ( HasServer api context-                    , HasContextEntry (context .++ DefaultErrorFormatters) ErrorFormatters )+                    , ServerContext context+                    )     => Proxy api -> Context context -> Server api -> Application-serveWithContext p context server =-  toApplication (runRouter format404 (route p context (emptyDelayed (Route server))))+serveWithContext p context = serveWithContextT p context id++-- | A general 'serve' function that allows you to pass a custom context and hoisting function to+-- apply on all routes.+serveWithContextT ::+  forall api context m.+  (HasServer api context, ServerContext context) =>+  Proxy api -> Context context -> (forall x. m x -> Handler x) -> ServerT api m -> Application+serveWithContextT p context toHandler server =+  toApplication (runRouter format404 (route p context (emptyDelayed router)))   where+    router = Route $ hoistServerWithContext p (Proxy :: Proxy context) toHandler server     format404 = notFoundErrorFormatter . getContextEntry . mkContextWithErrorFormatter $ context  -- | Hoist server implementation.
src/Servant/Server/Generic.hs view
@@ -1,12 +1,10 @@-{-# LANGUAGE ConstraintKinds      #-}-{-# LANGUAGE DataKinds            #-}-{-# LANGUAGE FlexibleContexts     #-}-{-# LANGUAGE KindSignatures       #-}-{-# LANGUAGE RankNTypes           #-}-{-# LANGUAGE ScopedTypeVariables  #-}-{-# LANGUAGE TypeFamilies         #-}-{-# LANGUAGE TypeOperators        #-}-{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE DataKinds              #-}+{-# LANGUAGE FlexibleContexts       #-}+{-# LANGUAGE RankNTypes             #-}+{-# LANGUAGE ScopedTypeVariables    #-}+{-# LANGUAGE TypeFamilies           #-}+{-# LANGUAGE TypeOperators          #-}+ -- | @since 0.14.1 module Servant.Server.Generic (     AsServerT,@@ -15,21 +13,15 @@     genericServeT,     genericServeTWithContext,     genericServer,-    genericServerT,+    genericServerT   ) where  import           Data.Proxy                  (Proxy (..)) -import           Servant.API.Generic import           Servant.Server---- | A type that specifies that an API record contains a server implementation.-data AsServerT (m :: * -> *)-instance GenericMode (AsServerT m) where-    type AsServerT m :- api = ServerT api m--type AsServer = AsServerT Handler+import           Servant.API.Generic+import           Servant.Server.Internal  -- | Transform a record of routes into a WAI 'Application'. genericServe@@ -97,3 +89,4 @@     => routes (AsServerT m)     -> ToServant routes (AsServerT m) genericServerT = toServant+
src/Servant/Server/Internal.hs view
@@ -1,23 +1,22 @@-{-# LANGUAGE CPP                   #-}+{-# LANGUAGE AllowAmbiguousTypes   #-} {-# LANGUAGE ConstraintKinds       #-} {-# LANGUAGE DataKinds             #-} {-# LANGUAGE DeriveDataTypeable    #-} {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE InstanceSigs          #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE PolyKinds             #-}+{-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE RankNTypes            #-} {-# LANGUAGE ScopedTypeVariables   #-} {-# LANGUAGE TupleSections         #-}+{-# LANGUAGE TypeApplications      #-} {-# LANGUAGE TypeFamilies          #-} {-# LANGUAGE TypeOperators         #-} {-# LANGUAGE UndecidableInstances  #-} -#if MIN_VERSION_base(4,9,0) && __GLASGOW_HASKELL__ >= 802-#define HAS_TYPE_ERROR-#endif- module Servant.Server.Internal   ( module Servant.Server.Internal   , module Servant.Server.Internal.BasicAuth@@ -42,12 +41,11 @@ import qualified Data.ByteString.Builder                    as BB import qualified Data.ByteString.Char8                      as BC8 import qualified Data.ByteString.Lazy                       as BL+import           Data.Constraint (Dict(..)) import           Data.Either                  (partitionEithers) import           Data.Maybe                  (fromMaybe, isNothing, mapMaybe, maybeToList)-import           Data.Semigroup-                 ((<>)) import           Data.String                  (IsString (..)) import           Data.String.Conversions@@ -56,8 +54,9 @@                  (Tagged (..), retag, untag) import qualified Data.Text                                  as T import           Data.Typeable+import           GHC.Generics import           GHC.TypeLits-                 (KnownNat, KnownSymbol, natVal, symbolVal)+                 (KnownNat, KnownSymbol, TypeError, symbolVal) import qualified Network.HTTP.Media                         as NHM import           Network.HTTP.Types                         hiding                  (Header, ResponseHeaders)@@ -77,7 +76,8 @@                  QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),                  RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,                  Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,-                 WithNamedContext)+                 WithNamedContext, NamedRoutes)+import           Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant) import           Servant.API.ContentTypes                  (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),                  AllMime, MimeRender (..), MimeUnrender (..), NoContent,@@ -87,7 +87,10 @@                  unfoldRequestArgument) import           Servant.API.ResponseHeaders                  (GetHeaders, Headers, getHeaders, getResponse)+import           Servant.API.Status+                 (statusFromNat) import qualified Servant.Types.SourceT                      as S+import           Servant.API.TypeErrors import           Web.HttpApiData                  (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,                  parseUrlPieces)@@ -103,12 +106,10 @@ import           Servant.Server.Internal.RoutingApplication import           Servant.Server.Internal.ServerError -#ifdef HAS_TYPE_ERROR import           GHC.TypeLits                  (ErrorMessage (..), TypeError) import           Servant.API.TypeLevel                  (AtLeastOneFragment, FragmentUnique)-#endif  class HasServer api context where   type ServerT api (m :: * -> *) :: *@@ -300,7 +301,7 @@    route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status     where method = reflectMethod (Proxy :: Proxy method)-          status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)+          status = statusFromNat (Proxy :: Proxy status)  instance {-# OVERLAPPING #-}          ( AllCTRender ctypes a, ReflectMethod method, KnownNat status@@ -312,7 +313,7 @@    route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status     where method = reflectMethod (Proxy :: Proxy method)-          status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)+          status = statusFromNat (Proxy :: Proxy status)  instance (ReflectMethod method) =>          HasServer (NoContentVerb method) context where@@ -333,7 +334,7 @@    route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)       where method = reflectMethod (Proxy :: Proxy method)-            status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)+            status = statusFromNat (Proxy :: Proxy status)   instance {-# OVERLAPPING #-}@@ -347,7 +348,7 @@    route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)       where method = reflectMethod (Proxy :: Proxy method)-            status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)+            status = statusFromNat (Proxy :: Proxy status)   streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>@@ -786,7 +787,7 @@ -- * helpers  ct_wildcard :: B.ByteString-ct_wildcard = "*" <> "/" <> "*" -- Because CPP+ct_wildcard = "*" <> "/" <> "*"  getAcceptHeader :: Request -> AcceptHeader getAcceptHeader = AcceptHeader . fromMaybe ct_wildcard . lookup hAccept . requestHeaders@@ -814,39 +815,15 @@   hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s  ---------------------------------------------------------------------------------- TypeError helpers+-- Custom type errors ------------------------------------------------------------------------------- -#ifdef HAS_TYPE_ERROR--- | This instance catches mistakes when there are non-saturated--- type applications on LHS of ':>'.------ >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")--- ...--- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.--- ...Maybe you haven't applied enough arguments to--- ...Capture' '[] "foo"--- ...------ >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)--- ...--- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.--- ...Maybe you haven't applied enough arguments to--- ...Capture' '[] "foo"--- ...----instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context+-- Erroring instance for 'HasServer' when a combinator is not fully applied+instance TypeError (PartialApplication HasServer arr) => HasServer ((arr :: a -> b) :> sub) context   where-    type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)-    -- it doesn't really matter what sub route we peak-    route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"-    hoistServerWithContext _ _ _ = id---- Cannot have TypeError here, otherwise use of this symbol will error :)-type HasServerArrowKindError arr =-    'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."-    ':$$: 'Text "Maybe you haven't applied enough arguments to"-    ':$$: 'ShowType arr+    type ServerT (arr :> sub) _ = TypeError (PartialApplication HasServer arr)+    route = error "unreachable"+    hoistServerWithContext _ _ _ _ = error "unreachable"  -- | This instance prevents from accidentally using '->' instead of ':>' --@@ -880,8 +857,16 @@     ':$$: 'ShowType a     ':$$: 'Text "and"     ':$$: 'ShowType b-#endif +-- Erroring instances for 'HasServer' for unknown API combinators++-- XXX: This omits the @context@ parameter, e.g.:+--+-- "There is no instance for HasServer (Bool :> …)". Do we care ?+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub HasServer ty) => HasServer (ty :> sub) context++instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context+ -- | Ignore @'Fragment'@ in server handlers. -- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details. --@@ -893,11 +878,7 @@ -- > server = getBooks -- >   where getBooks :: Handler [Book] -- >         getBooks = ...return all books...-#ifdef HAS_TYPE_ERROR instance (AtLeastOneFragment api, FragmentUnique (Fragment a1 :> api), HasServer api context)-#else-instance (HasServer api context)-#endif     => HasServer (Fragment a1 :> api) context where   type ServerT (Fragment a1 :> api) m = ServerT api m @@ -907,3 +888,72 @@  -- $setup -- >>> import Servant++-- | A type that specifies that an API record contains a server implementation.+data AsServerT (m :: * -> *)+instance GenericMode (AsServerT m) where+    type AsServerT m :- api = ServerT api m++type AsServer = AsServerT Handler+++-- | Set of constraints required to convert to / from vanilla server types.+type GServerConstraints api m =+  ( ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m+  , GServantProduct (Rep (api (AsServerT m)))+  )++-- | This class is a necessary evil: in the implementation of 'HasServer' for+--  @'NamedRoutes' api@, we essentially need the quantified constraint @forall+--  m. 'GServerConstraints' m@ to hold.+--+-- We cannot require do that directly as the definition of 'GServerConstraints'+-- contains type family applications ('Rep' and 'ServerT'). The trick is to hide+-- those type family applications behind a typeclass providing evidence for+-- @'GServerConstraints' api m@ in the form of a dictionary, and require that+-- @forall m. 'GServer' api m@ instead.+--+-- Users shouldn't have to worry about this class, as the only possible instance+-- is provided in this module for all record APIs.++class GServer (api :: * -> *) (m :: * -> *) where+  gServerProof :: Dict (GServerConstraints api m)++instance+  ( ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m+  , GServantProduct (Rep (api (AsServerT m)))+  ) => GServer api m where+  gServerProof = Dict++instance+  ( HasServer (ToServantApi api) context+  , forall m. Generic (api (AsServerT m))+  , forall m. GServer api m+  ) => HasServer (NamedRoutes api) context where++  type ServerT (NamedRoutes api) m = api (AsServerT m)++  route+    :: Proxy (NamedRoutes api)+    -> Context context+    -> Delayed env (api (AsServerT Handler))+    -> Router env+  route _ ctx delayed =+    case gServerProof @api @Handler of+      Dict -> route (Proxy @(ToServantApi api)) ctx (toServant <$> delayed)++  hoistServerWithContext+    :: forall m n. Proxy (NamedRoutes api)+    -> Proxy context+    -> (forall x. m x -> n x)+    -> api (AsServerT m)+    -> api (AsServerT n)+  hoistServerWithContext _ pctx nat server =+    case (gServerProof @api @m, gServerProof @api @n) of+      (Dict, Dict) ->+        fromServant servantSrvN+        where+          servantSrvM :: ServerT (ToServantApi api) m =+            toServant server+          servantSrvN :: ServerT (ToServantApi api) n =+            hoistServerWithContext (Proxy @(ToServantApi api)) pctx nat servantSrvM
src/Servant/Server/Internal/BasicAuth.hs view
@@ -12,8 +12,6 @@ import qualified Data.ByteString                     as BS import           Data.ByteString.Base64                  (decodeLenient)-import           Data.Monoid-                 ((<>)) import           Data.Typeable                  (Typeable) import           Data.Word8
src/Servant/Server/Internal/ServerError.hs view
@@ -187,7 +187,7 @@ -- Example: -- -- > failingHandler :: Handler ()--- > failingHandler = throwError $ err404 { errBody = "(╯°□°)╯︵ ┻━┻)." }+-- > failingHandler = throwError $ err404 { errBody = "Are you lost?" } -- err404 :: ServerError err404 = ServerError { errHTTPCode = 404
test/Servant/Server/ErrorSpec.hs view
@@ -12,8 +12,6 @@                  (encode) import qualified Data.ByteString.Char8      as BC import qualified Data.ByteString.Lazy.Char8 as BCL-import           Data.Monoid-                 ((<>)) import           Data.Proxy import           Data.String.Conversions                  (cs)
test/Servant/ServerSpec.hs view
@@ -7,6 +7,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies        #-} {-# LANGUAGE TypeOperators       #-}+{-# LANGUAGE TypeApplications    #-} {-# OPTIONS_GHC -freduction-depth=100 #-}  module Servant.ServerSpec where@@ -28,8 +29,6 @@                  (fromMaybe) import           Data.Proxy                  (Proxy (Proxy))-import           Data.SOP-                 (I (..), NS (..)) import           Data.String                  (fromString) import           Data.String.Conversions@@ -699,8 +698,8 @@        Capture "ok" Bool :> UVerb 'GET '[JSON] UVerbHeaderResponse  uverbResponseHeadersServer :: Server UVerbResponseHeadersApi-uverbResponseHeadersServer True = pure . Z . I . WithStatus $ addHeader 5 "foo"-uverbResponseHeadersServer False = pure . S . Z . I . WithStatus $ "bar"+uverbResponseHeadersServer True = respond . WithStatus @200 . addHeader @"H1" (5 :: Int) $ ("foo" :: String)+uverbResponseHeadersServer False = respond .  WithStatus @404 $ ("bar" :: String)  uverbResponseHeadersSpec :: Spec uverbResponseHeadersSpec = describe "UVerbResponseHeaders" $ do