packages feed

webgear-core 1.0.5 → 1.1.0

raw patch · 20 files changed

+838/−546 lines, 20 filesdep +binarydep +cookiedep +wai-extradep −safe-exceptionsdep −unordered-containersdep ~bytestringdep ~http-api-datadep ~text

Dependencies added: binary, cookie, wai-extra

Dependencies removed: safe-exceptions, unordered-containers

Dependency ranges changed: bytestring, http-api-data, text

Files

CHANGELOG.md view
@@ -2,6 +2,16 @@  ## [Unreleased] +## [1.1.0] - 2023-12-29++### Added+- Streaming responses support (#26)+- Support for cookies (#29)+- Support file uploads (#32)++### Changed+- Redesign APIs for ease of use (breaking change) (#24)+ ## [1.0.5] - 2023-05-04  ### Changed@@ -34,7 +44,8 @@ - Extracted webgear-core from webgear-server - New arrow based API -[Unreleased]: https://github.com/haskell-webgear/webgear/compare/v1.0.5...HEAD+[Unreleased]: https://github.com/haskell-webgear/webgear/compare/v1.1.0...HEAD+[1.1.0]: https://github.com/haskell-webgear/webgear/releases/tag/v1.1.0 [1.0.5]: https://github.com/haskell-webgear/webgear/releases/tag/v1.0.5 [1.0.4]: https://github.com/haskell-webgear/webgear/releases/tag/v1.0.4 [1.0.3]: https://github.com/haskell-webgear/webgear/releases/tag/v1.0.3
src/WebGear/Core.hs view
@@ -22,11 +22,13 @@   module WebGear.Core.Handler,   module WebGear.Core.Handler.Static,   module WebGear.Core.Traits,+  module WebGear.Core.MIMETypes, ) where  import Control.Arrow import Data.Text import qualified Network.Wai as Wai+import WebGear.Core.MIMETypes  import WebGear.Core.Handler import WebGear.Core.Handler.Static
src/WebGear/Core/Handler.hs view
@@ -10,17 +10,19 @@   RequestHandler,   Middleware,   routeMismatch,-  unlinkA,+  unwitnessA,+  (>->),+  (<-<), ) where -import Control.Arrow (ArrowChoice, ArrowPlus, arr)+import Control.Arrow (Arrow, ArrowChoice, ArrowPlus, arr) import Control.Arrow.Operations (ArrowError (..)) import Data.String (IsString) import Data.Text (Text) import GHC.Exts (IsList (..)) import WebGear.Core.Request (Request) import WebGear.Core.Response (Response (..))-import WebGear.Core.Trait (Linked (unlink))+import WebGear.Core.Trait (With (unwitness))  -- | Parts of the request path used by the routing machinery newtype RoutePath = RoutePath [Text]@@ -53,11 +55,11 @@   -- | Set a summary of a part of an API   setSummary :: Summary -> h a a --- | A handler arrow from a linked request to response.-type RequestHandler h req = h (Linked req Request) Response+-- | A handler arrow from a witnessed request to response.+type RequestHandler h ts = h (Request `With` ts) Response  -- | A middleware enhances a `RequestHandler` and produces another handler.-type Middleware h reqOut reqIn = RequestHandler h reqIn -> RequestHandler h reqOut+type Middleware h tsOut tsIn = RequestHandler h tsIn -> RequestHandler h tsOut  -- | Description associated with part of an API newtype Description = Description {getDescription :: Text}@@ -80,11 +82,47 @@   mempty = RouteMismatch  -- | Indicates that the request does not match the current handler.-routeMismatch :: ArrowError RouteMismatch h => h a b+routeMismatch :: (ArrowError RouteMismatch h) => h a b routeMismatch = proc _a -> raise -< RouteMismatch {-# INLINE routeMismatch #-} --- | Lifts `unlink` into a handler arrow.-unlinkA :: Handler h m => h (Linked ts Response) Response-unlinkA = arr unlink-{-# INLINE unlinkA #-}+-- | Lifts `unwitness` into a handler arrow.+unwitnessA :: (Handler h m) => h (Response `With` ts) Response+unwitnessA = arr unwitness+{-# INLINE unwitnessA #-}++infixr 1 >->, <-<++{- | Thread a response through commands from left to right.++ For example, an HTTP 200 response with a body and Content-Type header+ can be generated with:++@+ (ok200 -< ())+   >-> (\resp -> setBody "text/plain" -< (resp, "Hello World"))+   >-> (\resp -> unwitnessA -< resp)+@+-}+(>->) :: (Arrow h) => h (env, stack) a -> h (env, (a, stack)) b -> h (env, stack) b+f >-> g = proc (env, stack) -> do+  a <- f -< (env, stack)+  g -< (env, (a, stack))+{-# INLINE (>->) #-}++{- | Thread a response through commands from right to left.++ For example, an HTTP 200 response with a body and Content-Type header+ can be generated with:++@+ (\resp -> unwitnessA -< resp)+   <-< (\resp -> setBody "text/plain" -< (resp, "Hello World"))+   <-< (ok200 -< ())+@+-}+(<-<) :: (Arrow h) => h (env, (a, stack)) b -> h (env, stack) a -> h (env, stack) b+f <-< g = proc (env, stack) -> do+  a <- g -< (env, stack)+  f -< (env, (a, stack))+{-# INLINE (<-<) #-}
src/WebGear/Core/Handler/Static.hs view
@@ -7,26 +7,28 @@ ) where  import Control.Arrow ((<<<))-import Control.Exception.Safe (catchIO)-import Control.Monad.IO.Class (MonadIO (..))-import qualified Data.ByteString.Lazy as LBS import qualified Data.Text as Text import qualified Network.Mime as Mime import System.FilePath (joinPath, takeFileName, (</>))-import WebGear.Core.Handler (Handler (..), RoutePath (..), unlinkA)+import WebGear.Core.Handler (Handler (..), RoutePath (..), unwitnessA, (>->)) import WebGear.Core.Request (Request (..))-import WebGear.Core.Response (Response)-import WebGear.Core.Trait (Linked (..), Sets)-import WebGear.Core.Trait.Body (Body, setBodyWithoutContentType)-import WebGear.Core.Trait.Header (RequiredHeader, setHeader)+import WebGear.Core.Response (Response, ResponseBody (..))+import WebGear.Core.Trait (Sets, With)+import WebGear.Core.Trait.Body (UnknownContentBody, setBodyWithoutContentType)+import WebGear.Core.Trait.Header (RequiredResponseHeader, setHeader) import WebGear.Core.Trait.Status (Status, notFound404, ok200) import Prelude hiding (readFile)  -- | Serve files under the specified directory. serveDir ::-  ( MonadIO m-  , Handler h m-  , Sets h [Status, RequiredHeader "Content-Type" Mime.MimeType, Body LBS.ByteString] Response+  ( Handler h m+  , Sets+      h+      [ Status+      , RequiredResponseHeader "Content-Type" Mime.MimeType+      , UnknownContentBody+      ]+      Response   ) =>   -- | The directory to serve   FilePath ->@@ -34,28 +36,31 @@   -- response will be returned for requests to the root path if this   -- is set to @Nothing@.   Maybe FilePath ->-  h (Linked req Request) Response+  h (Request `With` ts) Response serveDir root index = proc _request -> consumeRoute go -< ()   where     go = proc path -> do       case (path, index) of-        (RoutePath [], Nothing) -> unlinkA <<< notFound404 -< ()+        (RoutePath [], Nothing) -> unwitnessA <<< notFound404 -< ()         (RoutePath [], Just f) -> serveFile -< root </> f         (RoutePath ps, _) -> serveFile -< root </> joinPath (Text.unpack <$> ps)  -- | Serve a file specified by the input filepath. serveFile ::-  ( MonadIO m-  , Handler h m-  , Sets h [Status, RequiredHeader "Content-Type" Mime.MimeType, Body LBS.ByteString] Response+  ( Handler h m+  , Sets+      h+      [ Status+      , RequiredResponseHeader "Content-Type" Mime.MimeType+      , UnknownContentBody+      ]+      Response   ) =>   h FilePath Response serveFile = proc file -> do-  maybeContents <- readFile -< file-  case maybeContents of-    Nothing -> unlinkA <<< notFound404 -< ()-    Just contents -> do-      let contentType = Mime.defaultMimeLookup $ Text.pack $ takeFileName file-      unlinkA <<< setHeader @"Content-Type" (setBodyWithoutContentType ok200) -< (contentType, (contents, ()))-  where-    readFile = arrM $ \f -> liftIO $ (Just <$> LBS.readFile f) `catchIO` const (pure Nothing)+  let contents = ResponseBodyFile file Nothing+      contentType = Mime.defaultMimeLookup $ Text.pack $ takeFileName file+  (ok200 -< ())+    >-> (\resp -> setBodyWithoutContentType -< (resp, contents))+    >-> (\resp -> setHeader @"Content-Type" -< (resp, contentType))+    >-> (\resp -> unwitnessA -< resp)
+ src/WebGear/Core/MIMETypes.hs view
@@ -0,0 +1,98 @@+-- | MIME types for HTTP bodies+module WebGear.Core.MIMETypes (+  MIMEType (..),+  FormURLEncoded (..),+  HTML (..),+  JSON (..),+  FormData (..),+  FormDataResult (..),+  OctetStream (..),+  PlainText (..),+) where++import Data.String (IsString (fromString))+import Data.Text (Text, unpack)+import qualified Network.HTTP.Media as HTTP+import qualified Network.Wai.Parse as Wai.Parse++-- | MIME types used in the Accept and Content-Type headers+class MIMEType mt where+  mimeType :: mt -> HTTP.MediaType++--------------------------------------------------------------------------------++-- | The application/x-www-form-urlencoded MIME type+data FormURLEncoded = FormURLEncoded++instance MIMEType FormURLEncoded where+  mimeType :: FormURLEncoded -> HTTP.MediaType+  mimeType FormURLEncoded = "application/x-www-form-urlencoded"+  {-# INLINE mimeType #-}++--------------------------------------------------------------------------------++-- | The text/html MIME type+data HTML = HTML++instance MIMEType HTML where+  mimeType :: HTML -> HTTP.MediaType+  mimeType HTML = "text/html"+  {-# INLINE mimeType #-}++--------------------------------------------------------------------------------++-- | A JSON MIME type with customizable media type+data JSON+  = -- | JSON with a specific media type+    JSONMedia Text+  | -- | application/json media type+    JSON++instance MIMEType JSON where+  mimeType :: JSON -> HTTP.MediaType+  mimeType =+    \case+      JSONMedia mt -> fromString (unpack mt)+      JSON -> "application/json"+  {-# INLINE mimeType #-}++--------------------------------------------------------------------------------++-- | The multipart/form-data MIME type+data FormData a = FormData+  { parseOptions :: Wai.Parse.ParseRequestBodyOptions+  , backendOptions :: Wai.Parse.BackEnd a+  }++{- | Result of parsing a multipart/form-data body from a request.+The body can contain both parameters and files.+-}+data FormDataResult a = FormDataResult+  { formDataParams :: [Wai.Parse.Param]+  , formDataFiles :: [Wai.Parse.File a]+  }++instance MIMEType (FormData a) where+  mimeType :: FormData a -> HTTP.MediaType+  mimeType _ = "multipart/form-data"+  {-# INLINE mimeType #-}++--------------------------------------------------------------------------------++-- | The application/octet-stream MIME type+data OctetStream = OctetStream++instance MIMEType OctetStream where+  mimeType :: OctetStream -> HTTP.MediaType+  mimeType OctetStream = "application/octet-stream"+  {-# INLINE mimeType #-}++--------------------------------------------------------------------------------++-- | The text/plain MIME type+data PlainText = PlainText++instance MIMEType PlainText where+  mimeType :: PlainText -> HTTP.MediaType+  mimeType PlainText = "text/plain"+  {-# INLINE mimeType #-}
src/WebGear/Core/Request.hs view
@@ -14,9 +14,11 @@   requestHeaders,   requestBodyLength,   getRequestBodyChunk,+  getRequestBody, ) where  import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as LBS import Data.List (find) import Data.Text (Text) import qualified Network.HTTP.Types as HTTP@@ -25,8 +27,8 @@  -- | A request processed by a handler newtype Request = Request-  { -- | underlying WAI request-    waiRequest :: Wai.Request+  { toWaiRequest :: Wai.Request+  -- ^ underlying WAI request   }  -- | Get the value of a request header@@ -35,36 +37,40 @@  -- | See 'Wai.getRequestBodyChunk' getRequestBodyChunk :: Request -> IO ByteString-getRequestBodyChunk = Wai.getRequestBodyChunk . waiRequest+getRequestBodyChunk = Wai.getRequestBodyChunk . toWaiRequest +-- | Returns the entire request body as a lazy bytestring+getRequestBody :: Request -> IO LBS.ByteString+getRequestBody = Wai.lazyRequestBody . toWaiRequest+ -- | See 'Wai.httpVersion' httpVersion :: Request -> HTTP.HttpVersion-httpVersion = Wai.httpVersion . waiRequest+httpVersion = Wai.httpVersion . toWaiRequest  -- | See 'Wai.isSecure' isSecure :: Request -> Bool-isSecure = Wai.isSecure . waiRequest+isSecure = Wai.isSecure . toWaiRequest  -- | See 'Wai.pathInfo' pathInfo :: Request -> [Text]-pathInfo = Wai.pathInfo . waiRequest+pathInfo = Wai.pathInfo . toWaiRequest  -- | See 'Wai.queryString' queryString :: Request -> HTTP.Query-queryString = Wai.queryString . waiRequest+queryString = Wai.queryString . toWaiRequest  -- | See 'Wai.remoteHost' remoteHost :: Request -> SockAddr-remoteHost = Wai.remoteHost . waiRequest+remoteHost = Wai.remoteHost . toWaiRequest  -- | See 'Wai.requestBodyLength' requestBodyLength :: Request -> Wai.RequestBodyLength-requestBodyLength = Wai.requestBodyLength . waiRequest+requestBodyLength = Wai.requestBodyLength . toWaiRequest  -- | See 'Wai.requestHeaders' requestHeaders :: Request -> HTTP.RequestHeaders-requestHeaders = Wai.requestHeaders . waiRequest+requestHeaders = Wai.requestHeaders . toWaiRequest  -- | See 'Wai.requestMethod' requestMethod :: Request -> HTTP.Method-requestMethod = Wai.requestMethod . waiRequest+requestMethod = Wai.requestMethod . toWaiRequest
src/WebGear/Core/Response.hs view
@@ -4,31 +4,40 @@ module WebGear.Core.Response (   -- * Basic Types   Response (..),+  ResponseBody (..),   toWaiResponse, ) where -import Data.ByteString (ByteString)-import qualified Data.ByteString.Lazy as LBS-import qualified Data.HashMap.Strict as HM-import Data.Maybe (fromMaybe)+import qualified Data.Binary.Builder as B import qualified Network.HTTP.Types as HTTP import qualified Network.Wai as Wai --- | An HTTP response sent from the server to the client.------ The response contains a status, optional headers and an optional--- body payload.+{- | An HTTP response sent from the server to the client.++The response contains a status, optional headers and an optional+body payload.+-} data Response = Response-  { -- | Response status code-    responseStatus :: HTTP.Status-  , -- | Response headers-    responseHeaders :: HM.HashMap HTTP.HeaderName ByteString-  , -- | Optional response body-    responseBody :: Maybe LBS.ByteString+  { responseStatus :: HTTP.Status+  -- ^ Response status code+  , responseHeaders :: HTTP.ResponseHeaders+  -- ^ Response headers+  , responseBody :: ResponseBody+  -- ^ The response body   }-  deriving stock (Eq, Ord, Show) +data ResponseBody+  = ResponseBodyFile FilePath (Maybe Wai.FilePart)+  | ResponseBodyBuilder B.Builder+  | ResponseBodyStream Wai.StreamingBody+ -- | Generate a WAI response toWaiResponse :: Response -> Wai.Response toWaiResponse Response{..} =-  Wai.responseLBS responseStatus (HM.toList responseHeaders) (fromMaybe "" responseBody)+  case responseBody of+    ResponseBodyFile fpath fpart ->+      Wai.responseFile responseStatus responseHeaders fpath fpart+    ResponseBodyBuilder builder ->+      Wai.responseBuilder responseStatus responseHeaders builder+    ResponseBodyStream stream ->+      Wai.responseStream responseStatus responseHeaders stream
src/WebGear/Core/Trait.hs view
@@ -3,15 +3,45 @@ {- | Traits are optional attributes associated with a value. For  example, a list containing totally ordered values might have a  @Maximum@ trait where the associated attribute is the maximum- value. This trait exists only if the list is non-empty. The `Trait`+ value. This trait exists only if the list is non-empty. The 'Trait'  typeclass provides an interface to extract such trait attributes. - Traits help to link attributes with values in a type-safe manner.+ Traits help to associate attributes with values in a type-safe+ manner.   Traits are somewhat similar to [refinement  types](https://hackage.haskell.org/package/refined), but allow- arbitrary attributes to be associated with a value instead of only- a predicate.+ arbitrary attributes to be associated with a value instead of only a+ predicate.++ A value @a@ associated with traits @ts@ is referred to as a witnessed+ value, represented by the type @a \`'With'\` ts@ where @ts@ is a+ type-level list. You can extract a trait attribute from a witnessed+ value with:++ @+ `pick` \@t (`from` witnessedValue)+ @++ The above expression will result in a compile-time error if @t@ is+ not present in the type-level list of the witnessed value's type.++ You can create a witnessed value in a number of ways:++ First, you can use 'wzero' to lift a regular value to a witnessed+ value with no associated traits.++ Second, you can use 'probe' to search for the presence of a trait and+ add it to the witnessed value; this will adjust the type-level list+ accordingly. This is used in cases where the regular value already+ contains the trait attribute which can be extracted using the 'Get'+ typeclass.++ Third, you can use 'plant' to add a trait attribute to a witnessed+ value, thereby extending its type-level list with one more+ trait. This is used in cases where you want to modify the witnessed+ value. This operation requires an implementation of the 'Set'+ typeclass. -} module WebGear.Core.Trait (   -- * Core Types@@ -19,18 +49,18 @@   TraitAbsence (..),   Get (..),   Gets,-  Linked,   Set (..),   Sets,+  With, -  -- * Linking values with attributes-  linkzero,-  linkminus,-  unlink,+  -- * Associating values with attributes+  wzero,+  wminus,+  unwitness,   probe,   plant, -  -- * Retrive trait attributes from linked values+  -- * Retrieve trait attributes from witnessed values   HasTrait (..),   HaveTraits,   pick,@@ -49,33 +79,34 @@   type Attribute t a :: Type  -- | A trait @t@ that can be retrieved from @a@ but could be absent.-class Trait t a => TraitAbsence t a where+class (Trait t a) => TraitAbsence t a where   -- | Type that indicates that the trait does not exist for a   -- value. This could be an error message, exception etc.   type Absence t a :: Type  -- | Extract trait attributes from a value. class (Arrow h, TraitAbsence t a) => Get h t a where-  -- | Attempt to deduce the trait attribute from the value @a@.+  -- | Attempt to witness the trait attribute from the value @a@.   getTrait ::-    -- | The trait to extract+    -- | The trait to witness     t ->-    -- | Arrow that extracts the trait and can possibly fail-    h (Linked ts a) (Either (Absence t a) (Attribute t a))+    -- | Arrow that attemtps to witness the trait and can possibly+    -- fail+    h (a `With` ts) (Either (Absence t a) (Attribute t a)) --- | Set a trait attribute on a value+-- | Associate a trait attribute on a value class (Arrow h, Trait t a) => Set h (t :: Type) a where-  -- | Set a trait attribute @t@ on the value @a@.+  -- | Set a trait attribute @t@ on the value @a \`With\` ts@.   setTrait ::     -- | The trait to set     t ->-    -- | A function to generate a linked value. This function must be-    -- called by the `setTrait` implementation to generate a linked+    -- | A function to generate a witnessed value. This function must+    -- be called by the `setTrait` implementation to generate a+    -- witnessed value.+    (a `With` ts -> a -> Attribute t a -> a `With` (t : ts)) ->+    -- | An arrow that attaches a new trait attribute to a witnessed     -- value.-    (Linked ts a -> a -> Attribute t a -> Linked (t : ts) a) ->-    -- | An arrow that attches a new trait attribute to a value linked-    -- with other traits-    h (Linked ts a, Attribute t a) (Linked (t : ts) a)+    h (a `With` ts, Attribute t a) (a `With` (t : ts))  {- | @Gets h ts a@ is equivalent to @(Get h t1 a, Get h t2 a, ..., Get  h tn a)@ where @ts = [t1, t2, ..., tn]@.@@ -91,96 +122,125 @@   Sets h '[] a = ()   Sets h (t : ts) a = (Set h t a, Sets h ts a) --- | A value linked with a type-level list of traits.-data Linked (ts :: [Type]) a = Linked-  { linkAttribute :: !(LinkedAttributes ts a)-  , unlink :: !a-  -- ^ Retrive the value from a linked value+{- | A value associated with a list of traits, referred to as a+witnessed value. Typically, this is used as an infix type constructor:++@+a \`With\` ts+@++where @a@ is a value and @ts@ is a list of traits associated with+that value.++If @t@ is a type present in the list of types @ts@, it is possible to+extract its attribute from a witnessed value:++@+let witnessedValue :: a \`With\` ts+    witnessedValue = ...++let attr :: `Attribute` t a+    attr = `pick` \@t (`from` witnessedValue)+@+-}+data With a (ts :: [Type]) = With+  { attribute :: !(WitnessedAttribute ts a)+  , unwitness :: !a+  -- ^ Retrieve the value   } -type family LinkedAttributes (ts :: [Type]) (a :: Type) where-  LinkedAttributes '[] a = ()-  LinkedAttributes (t : ts) a = (Attribute t a, LinkedAttributes ts a)+type family WitnessedAttribute (ts :: [Type]) (a :: Type) where+  WitnessedAttribute '[] a = ()+  WitnessedAttribute (t : ts) a = (Attribute t a, WitnessedAttribute ts a) --- | Wrap a value with an empty list of traits.-linkzero :: a -> Linked '[] a-linkzero = Linked ()-{-# INLINE linkzero #-}+-- | Lift a value to a witnessed value having no associated traits.+wzero :: a -> a `With` '[]+wzero = With ()+{-# INLINE wzero #-}  -- | Forget the head trait-linkminus :: Linked (t : ts) a -> Linked ts a-linkminus (Linked (_, rv) a) = Linked rv a-{-# INLINE linkminus #-}+wminus :: a `With` (t : ts) -> a `With` ts+wminus (With (_, rv) a) = With rv a+{-# INLINE wminus #-} -{- | Attempt to link an additional trait with an already linked- value. This can fail indicating an 'Absence' of the trait.+{- | Attempt to witness an additional trait with a witnessed value. This+ can fail indicating an 'Absence' of the trait. -} probe ::   forall t ts h a.-  Get h t a =>+  (Get h t a) =>   t ->-  h (Linked ts a) (Either (Absence t a) (Linked (t : ts) a))+  h (a `With` ts) (Either (Absence t a) (a `With` (t : ts))) probe t = proc l -> do   res <- getTrait t -< l-  arr link -< (l, res)+  arr add -< (l, res)   where-    link :: (Linked ts a, Either e (Attribute t a)) -> Either e (Linked (t : ts) a)-    link (_, Left e) = Left e-    link (Linked{..}, Right attr) = Right $ Linked{linkAttribute = (attr, linkAttribute), ..}+    add :: (a `With` ts, Either e (Attribute t a)) -> Either e (a `With` (t : ts))+    add (_, Left e) = Left e+    add (With{..}, Right attr) = Right $ With{attribute = (attr, attribute), ..} {-# INLINE probe #-} -{- | Set a trait attribute on linked value to produce another linked- value+{- | Set a trait attribute on witnessed value to produce another+   witnessed value with the additional trait attached to it. -}-plant :: forall t ts h a. Set h t a => t -> h (Linked ts a, Attribute t a) (Linked (t : ts) a)+plant ::+  forall t ts h a.+  (Set h t a) =>+  t ->+  h (a `With` ts, Attribute t a) (a `With` (t : ts)) plant t = proc (l, attr) -> do-  setTrait t link -< (l, attr)+  setTrait t add -< (l, attr)   where-    link :: Linked ts a -> a -> Attribute t a -> Linked (t : ts) a-    link Linked{..} a' attr = Linked{linkAttribute = (attr, linkAttribute), unlink = a'}+    add :: a `With` ts -> a -> Attribute t a -> a `With` (t : ts)+    add With{..} a' attr = With{attribute = (attr, attribute), unwitness = a'} {-# INLINE plant #-}  {- | Constraint that proves that the trait @t@ is present in the list  of traits @ts@. -} class HasTrait t ts where-  -- | Get the attribute associated with @t@ from a linked value. See also: 'pick'.-  from :: Linked ts a -> Tagged t (Attribute t a)+  -- | Get the attribute associated with @t@ from a witnessed+  -- value. See also: 'pick'.+  from :: a `With` ts -> Tagged t (Attribute t a)  instance HasTrait t (t : ts) where-  from :: Linked (t : ts) a -> Tagged t (Attribute t a)-  from (Linked (lv, _) _) = Tagged lv+  from :: a `With` (t : ts) -> Tagged t (Attribute t a)+  from (With (lv, _) _) = Tagged lv   {-# INLINE from #-} -instance {-# OVERLAPPABLE #-} HasTrait t ts => HasTrait t (t' : ts) where-  from :: Linked (t' : ts) a -> Tagged t (Attribute t a)-  from l = from $ linkminus l+instance {-# OVERLAPPABLE #-} (HasTrait t ts) => HasTrait t (t' : ts) where+  from :: a `With` (t' : ts) -> Tagged t (Attribute t a)+  from l = from $ wminus l   {-# INLINE from #-}  {- | Retrieve a trait. - @pick@ is used along with `from` to retrieve an attribute from a- linked value:+ @pick@ is used along with 'from' to retrieve an attribute from a+ witnessed value: - > pick @t $ from val+ @+ pick @t (`from` val)+ @ -} pick :: Tagged t a -> a pick = untag {-# INLINE pick #-}  -- For better type errors-instance TypeError (MissingTrait t) => HasTrait t '[] where+instance (TypeError (MissingTrait t)) => HasTrait t '[] where   from = undefined  -- | Type error for nicer UX of missing traits type MissingTrait t =-  Text "The request doesn't have the trait ‘" :<>: ShowType t :<>: Text "’."+  Text "The value doesn't have the ‘"+    :<>: ShowType t+    :<>: Text "’ trait."     :$$: Text ""-    :$$: Text "Did you use a wrong trait type?"-    :$$: Text "For e.g., ‘QueryParam \"foo\" Int’ instead of ‘QueryParam \"foo\" String’?"+    :$$: Text "Did you forget to apply an appropriate middleware?"+    :$$: Text "For e.g. The trait ‘Body JSON t’ requires ‘requestBody @t JSON’ middleware."     :$$: Text ""-    :$$: Text "Or did you forget to apply an appropriate middleware?"-    :$$: Text "For e.g. The trait ‘JSONBody t’ can be used with ‘jsonRequestBody @t’ middleware."+    :$$: Text "or did you use a wrong trait type?"+    :$$: Text "For e.g., ‘RequiredQueryParam \"foo\" Int’ instead of ‘RequiredQueryParam \"foo\" String’?"     :$$: Text ""  {- | Constraint that proves that all the traits in the list @ts@ are
src/WebGear/Core/Trait/Auth/Basic.hs view
@@ -22,7 +22,7 @@  For example, given this handler:   @- myHandler :: ('Handler' h IO, 'HasTrait' ('BasicAuth' IO () 'Credentials') req) => 'RequestHandler' h req+ myHandler :: ('Handler' h IO, 'HasTrait' ('BasicAuth' IO () 'Credentials') ts) => 'RequestHandler' h ts  myHandler = ....  @ @@ -32,10 +32,10 @@  authConfig :: 'BasicAuth' IO () 'Credentials'  authConfig = 'BasicAuth'' { toBasicAttribute = pure . Right } - type ErrorTraits = [Status, RequiredHeader \"Content-Type\" Text, RequiredHeader \"WWW-Authenticate\" Text, Body Text]+ type ErrorTraits = [Status, RequiredRequestHeader \"Content-Type\" Text, RequiredRequestHeader \"WWW-Authenticate\" Text, Body Text]   errorHandler :: ('Handler' h IO, Sets h ErrorTraits Response)-              => h (Linked req Request, 'BasicAuthError' e) Response+              => h (Request \`With\` ts, 'BasicAuthError' e) Response  errorHandler = 'respondUnauthorized' \"Basic\" \"MyRealm\"  @ @@ -43,7 +43,7 @@   @  myHandlerWithAuth :: ('Handler' h IO, Get h ('BasicAuth' IO () 'Credentials') Request, Sets h ErrorTraits Response)-                   => 'RequestHandler' h req+                   => 'RequestHandler' h ts  myHandlerWithAuth = 'basicAuth' authConfig errorHandler myHandler  @ @@ -92,8 +92,8 @@  -- | Trait for HTTP basic authentication: https://tools.ietf.org/html/rfc7617 newtype BasicAuth' (x :: Existence) (scheme :: Symbol) m e a = BasicAuth'-  { -- | Convert the credentials to the trait attribute or an error-    toBasicAttribute :: Credentials -> m (Either e a)+  { toBasicAttribute :: Credentials -> m (Either e a)+  -- ^ Convert the credentials to the trait attribute or an error   }  -- | Trait for HTTP basic authentication with the "Basic" scheme.@@ -139,8 +139,8 @@ basicAuthMiddleware ::   (Get h (BasicAuth' x scheme m e t) Request, ArrowChoice h) =>   BasicAuth' x scheme m e t ->-  h (Linked req Request, Absence (BasicAuth' x scheme m e t) Request) Response ->-  Middleware h req (BasicAuth' x scheme m e t : req)+  h (Request `With` ts, Absence (BasicAuth' x scheme m e t) Request) Response ->+  Middleware h ts (BasicAuth' x scheme m e t : ts) basicAuthMiddleware authCfg errorHandler nextHandler =   proc request -> do     result <- probe authCfg -< request@@ -160,13 +160,13 @@  retrieved successfully. -} basicAuth ::-  forall m e t h req.+  forall m e t h ts.   (Get h (BasicAuth' Required "Basic" m e t) Request, ArrowChoice h) =>   -- | Authentication configuration   BasicAuth m e t ->   -- | Error handler-  h (Linked req Request, BasicAuthError e) Response ->-  Middleware h req (BasicAuth m e t : req)+  h (Request `With` ts, BasicAuthError e) Response ->+  Middleware h ts (BasicAuth m e t : ts) basicAuth = basicAuth' {-# INLINE basicAuth #-} @@ -177,13 +177,13 @@  > basicAuth' @"scheme" cfg errorHandler nextHandler -} basicAuth' ::-  forall scheme m e t h req.+  forall scheme m e t h ts.   (Get h (BasicAuth' Required scheme m e t) Request, ArrowChoice h) =>   -- | Authentication configuration   BasicAuth' Required scheme m e t ->   -- | Error handler-  h (Linked req Request, BasicAuthError e) Response ->-  Middleware h req (BasicAuth' Required scheme m e t : req)+  h (Request `With` ts, BasicAuthError e) Response ->+  Middleware h ts (BasicAuth' Required scheme m e t : ts) basicAuth' = basicAuthMiddleware {-# INLINE basicAuth' #-} @@ -199,11 +199,11 @@  authentication error appropriately. -} optionalBasicAuth ::-  forall m e t h req.+  forall m e t h ts.   (Get h (BasicAuth' Optional "Basic" m e t) Request, ArrowChoice h) =>   -- | Authentication configuration   BasicAuth' Optional "Basic" m e t ->-  Middleware h req (BasicAuth' Optional "Basic" m e t : req)+  Middleware h ts (BasicAuth' Optional "Basic" m e t : ts) optionalBasicAuth = optionalBasicAuth' {-# INLINE optionalBasicAuth #-} @@ -215,10 +215,10 @@  > optionalBasicAuth' @"scheme" cfg nextHandler -} optionalBasicAuth' ::-  forall scheme m e t h req.+  forall scheme m e t h ts.   (Get h (BasicAuth' Optional scheme m e t) Request, ArrowChoice h) =>   -- | Authentication configuration   BasicAuth' Optional scheme m e t ->-  Middleware h req (BasicAuth' Optional scheme m e t : req)+  Middleware h ts (BasicAuth' Optional scheme m e t : ts) optionalBasicAuth' cfg = basicAuthMiddleware cfg $ arr (absurd . snd) {-# INLINE optionalBasicAuth' #-}
src/WebGear/Core/Trait/Auth/Common.hs view
@@ -9,7 +9,7 @@   respondUnauthorized, ) where -import Control.Arrow (returnA, (<<<))+import Control.Arrow (returnA) import Data.ByteString (ByteString, drop) import Data.ByteString.Char8 (break) import Data.CaseInsensitive (CI, mk, original)@@ -19,20 +19,20 @@ import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Data.Void (absurd) import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)-import qualified Network.HTTP.Types as HTTP import Web.HttpApiData (FromHttpApiData (..))-import WebGear.Core.Handler (Handler, unlinkA)+import WebGear.Core.Handler (Handler, unwitnessA, (>->))+import WebGear.Core.MIMETypes (PlainText (..)) import WebGear.Core.Modifiers (Existence (..), ParseStyle (..)) import WebGear.Core.Request (Request) import WebGear.Core.Response (Response)-import WebGear.Core.Trait (Get (..), Linked, Sets)-import WebGear.Core.Trait.Body (Body, respondA)-import WebGear.Core.Trait.Header (Header (..), RequiredHeader, setHeader)-import WebGear.Core.Trait.Status (Status)+import WebGear.Core.Trait (Get (..), Sets, With)+import WebGear.Core.Trait.Body (Body, setBody)+import WebGear.Core.Trait.Header (RequestHeader (..), RequiredResponseHeader, setHeader)+import WebGear.Core.Trait.Status (Status, unauthorized401) import Prelude hiding (break, drop)  -- | Trait for \"Authorization\" header-type AuthorizationHeader scheme = Header Optional Lenient "Authorization" (AuthToken scheme)+type AuthorizationHeader scheme = RequestHeader Optional Lenient "Authorization" (AuthToken scheme)  {- | Extract the \"Authorization\" header from a request by specifying    an authentication scheme.@@ -41,10 +41,10 @@ -} getAuthorizationHeaderTrait ::   forall scheme h ts.-  Get h (AuthorizationHeader scheme) Request =>-  h (Linked ts Request) (Maybe (Either Text (AuthToken scheme)))+  (Get h (AuthorizationHeader scheme) Request) =>+  h (Request `With` ts) (Maybe (Either Text (AuthToken scheme))) getAuthorizationHeaderTrait = proc request -> do-  result <- getTrait (Header :: Header Optional Lenient "Authorization" (AuthToken scheme)) -< request+  result <- getTrait (RequestHeader :: RequestHeader Optional Lenient "Authorization" (AuthToken scheme)) -< request   returnA -< either absurd id result {-# INLINE getAuthorizationHeaderTrait #-} @@ -54,13 +54,13 @@  -- | The components of Authorization request header data AuthToken (scheme :: Symbol) = AuthToken-  { -- | Authentication scheme-    authScheme :: CI ByteString-  , -- | Authentication token-    authToken :: ByteString+  { authScheme :: CI ByteString+  -- ^ Authentication scheme+  , authToken :: ByteString+  -- ^ Authentication token   } -instance KnownSymbol scheme => FromHttpApiData (AuthToken scheme) where+instance (KnownSymbol scheme) => FromHttpApiData (AuthToken scheme) where   parseUrlPiece = parseHeader . encodeUtf8    {-# INLINE parseHeader #-}@@ -83,9 +83,9 @@   , Sets       h       [ Status-      , RequiredHeader "Content-Type" Text-      , RequiredHeader "WWW-Authenticate" Text-      , Body Text+      , RequiredResponseHeader "Content-Type" Text+      , RequiredResponseHeader "WWW-Authenticate" Text+      , Body PlainText Text       ]       Response   ) =>@@ -96,8 +96,8 @@   h a Response respondUnauthorized scheme (Realm realm) = proc _ -> do   let headerVal = decodeUtf8 $ original scheme <> " realm=\"" <> realm <> "\""-  unlinkA-    <<< setHeader @"WWW-Authenticate" (respondA HTTP.unauthorized401 "text/plain")-    -<-      (headerVal, "Unauthorized" :: Text)+  (unauthorized401 -< ())+    >-> (\resp -> setBody PlainText -< (resp, "Unauthorized" :: Text))+    >-> (\resp -> setHeader @"WWW-Authenticate" -< (resp, headerVal))+    >-> (\resp -> unwitnessA -< resp) {-# INLINE respondUnauthorized #-}
src/WebGear/Core/Trait/Auth/JWT.hs view
@@ -22,7 +22,7 @@  For example, given this handler:   @- myHandler :: ('Handler' h IO, 'HasTrait' ('JWTAuth' IO () 'JWT.ClaimsSet') req) => 'RequestHandler' h req+ myHandler :: ('Handler' h IO, 'HasTrait' ('JWTAuth' IO () 'JWT.ClaimsSet') ts) => 'RequestHandler' h ts  myHandler = ....  @ @@ -36,10 +36,10 @@    , toJWTAttribute = pure . Right    } - type ErrorTraits = [Status, RequiredHeader \"Content-Type\" Text, RequiredHeader \"WWW-Authenticate\" Text, Body Text]+ type ErrorTraits = [Status, RequiredRequestHeader \"Content-Type\" Text, RequiredRequestHeader \"WWW-Authenticate\" Text, Body Text]   errorHandler :: ('Handler' h IO, Sets h ErrorTraits Response)-              => h (Linked req Request, 'JWTAuthError' e) Response+              => h (Request \`With\` ts, 'JWTAuthError' e) Response  errorHandler = 'respondUnauthorized' \"Bearer\" \"MyRealm\"  @ @@ -47,7 +47,7 @@   @  myHandlerWithAuth :: ('Handler' h IO, Get h ('JWTAuth' IO () 'JWT.ClaimsSet') Request, Sets h ErrorTraits Response)-                   => 'RequestHandler' h req+                   => 'RequestHandler' h ts  myHandlerWithAuth = 'jwtAuth' authConfig errorHandler myHandler  @ @@ -97,12 +97,12 @@  \"Bearer\" scheme. -} data JWTAuth' (x :: Existence) (scheme :: Symbol) m e a = JWTAuth'-  { -- | Settings to validate the JWT-    jwtValidationSettings :: JWT.JWTValidationSettings-  , -- | JWK to validate the JWT-    jwkSet :: JWT.JWKSet-  , -- | Convert the claims set to the trait attribute or an error-    toJWTAttribute :: JWT.ClaimsSet -> m (Either e a)+  { jwtValidationSettings :: JWT.JWTValidationSettings+  -- ^ Settings to validate the JWT+  , jwkSet :: JWT.JWKSet+  -- ^ JWK to validate the JWT+  , toJWTAttribute :: JWT.ClaimsSet -> m (Either e a)+  -- ^ Convert the claims set to the trait attribute or an error   }  -- | Trait for JWT authentication with the \"Bearer\" scheme@@ -116,16 +116,16 @@   | JWTAuthAttributeError e   deriving stock (Eq, Show) -instance WebGear.Core.Trait.Trait (JWTAuth' Required scheme m e a) Request where+instance Trait (JWTAuth' Required scheme m e a) Request where   type Attribute (JWTAuth' Required scheme m e a) Request = a -instance WebGear.Core.Trait.TraitAbsence (JWTAuth' Required scheme m e a) Request where+instance TraitAbsence (JWTAuth' Required scheme m e a) Request where   type Absence (JWTAuth' Required scheme m e a) Request = JWTAuthError e -instance WebGear.Core.Trait.Trait (JWTAuth' Optional scheme m e a) Request where+instance Trait (JWTAuth' Optional scheme m e a) Request where   type Attribute (JWTAuth' Optional scheme m e a) Request = Either (JWTAuthError e) a -instance WebGear.Core.Trait.TraitAbsence (JWTAuth' Optional scheme m e a) Request where+instance TraitAbsence (JWTAuth' Optional scheme m e a) Request where   type Absence (JWTAuth' Optional scheme m e a) Request = Void  {- | Middleware to add JWT authentication protection for a@@ -143,14 +143,14 @@  retrieved successfully. -} jwtAuth ::-  ( WebGear.Core.Trait.Get h (JWTAuth m e t) Request+  ( Get h (JWTAuth m e t) Request   , ArrowChoice h   ) =>   -- | Authentication configuration   JWTAuth m e t ->   -- | Error handler-  h (WebGear.Core.Trait.Linked req Request, JWTAuthError e) Response ->-  Middleware h req (JWTAuth m e t : req)+  h (Request `With` ts, JWTAuthError e) Response ->+  Middleware h ts (JWTAuth m e t : ts) jwtAuth = jwtAuth' @"Bearer" {-# INLINE jwtAuth #-} @@ -170,26 +170,26 @@  authentication error appropriately. -} optionalJWTAuth ::-  ( WebGear.Core.Trait.Get h (JWTAuth' Optional "Bearer" m e t) Request+  ( Get h (JWTAuth' Optional "Bearer" m e t) Request   , ArrowChoice h   ) =>   -- | Authentication configuration   JWTAuth' Optional "Bearer" m e t ->-  Middleware h req (JWTAuth' Optional "Bearer" m e t : req)+  Middleware h ts (JWTAuth' Optional "Bearer" m e t : ts) optionalJWTAuth = optionalJWTAuth' @"Bearer" {-# INLINE optionalJWTAuth #-}  jwtAuthMiddleware ::-  forall s e t x h m req.-  ( WebGear.Core.Trait.Get h (JWTAuth' x s m e t) Request+  forall s e t x h m ts.+  ( Get h (JWTAuth' x s m e t) Request   , ArrowChoice h   ) =>   JWTAuth' x s m e t ->-  h (WebGear.Core.Trait.Linked req Request, WebGear.Core.Trait.Absence (JWTAuth' x s m e t) Request) Response ->-  Middleware h req (JWTAuth' x s m e t : req)+  h (Request `With` ts, Absence (JWTAuth' x s m e t) Request) Response ->+  Middleware h ts (JWTAuth' x s m e t : ts) jwtAuthMiddleware authCfg errorHandler nextHandler =   proc request -> do-    result <- WebGear.Core.Trait.probe authCfg -< request+    result <- probe authCfg -< request     case result of       Left err -> errorHandler -< (request, err)       Right val -> nextHandler -< val@@ -210,15 +210,15 @@  retrieved successfully. -} jwtAuth' ::-  forall s e t h m req.-  ( WebGear.Core.Trait.Get h (JWTAuth' Required s m e t) Request+  forall s e t h m ts.+  ( Get h (JWTAuth' Required s m e t) Request   , ArrowChoice h   ) =>   -- | Authentication configuration   JWTAuth' Required s m e t ->   -- | Error handler-  h (WebGear.Core.Trait.Linked req Request, JWTAuthError e) Response ->-  Middleware h req (JWTAuth' Required s m e t : req)+  h (Request `With` ts, JWTAuthError e) Response ->+  Middleware h ts (JWTAuth' Required s m e t : ts) jwtAuth' = jwtAuthMiddleware {-# INLINE jwtAuth' #-} @@ -238,12 +238,12 @@  authentication error appropriately. -} optionalJWTAuth' ::-  forall s e t h m req.-  ( WebGear.Core.Trait.Get h (JWTAuth' Optional s m e t) Request+  forall s e t h m ts.+  ( Get h (JWTAuth' Optional s m e t) Request   , ArrowChoice h   ) =>   -- | Authentication configuration   JWTAuth' Optional s m e t ->-  Middleware h req (JWTAuth' Optional s m e t : req)+  Middleware h ts (JWTAuth' Optional s m e t : ts) optionalJWTAuth' cfg = jwtAuthMiddleware cfg $ arr (absurd . snd) {-# INLINE optionalJWTAuth' #-}
src/WebGear/Core/Trait/Body.hs view
@@ -1,86 +1,70 @@ {- | Traits and middlewares to handle request and response body    payloads. - There are a number of ways to extract a body from a request:-  The 'requestBody' middleware attempts to convert the body to a  Haskell value or invoke an error handler if that fails. - The 'jsonRequestBody' middleware attempts to convert a JSON- formatted body to a Haskell value or invoke an error handler if that- fails. It uses the standard "application/json" media type.-- The 'jsonRequestBody'' middleware is similar but supports custom- media types.-- Similarly, there are a number of ways to set a response body:-- The easiest option is to use one of 'respondA', 'respondJsonA', or- 'respondJsonA'' middlewares. These middlewares generate a response- from an HTTP status and a response body.+ The 'respondA' middleware generates a response from an HTTP status+ and a response body. - If you need finer control over setting the body, use one of- 'setBody', 'setBodyWithoutContentType', 'setJSONBody',- 'setJSONBodyWithoutContentType', or 'setJSONBody''. These arrows- accept a linked response and a body and sets the body in the- response. You can generate an input response object using functions- from "WebGear.Core.Trait.Status" module.+ If you need finer control over setting the body, use 'setBody' or+ 'setBodyWithoutContentType'. These arrows accept a witnessed response+ and a body and sets the body in the response. You can generate an+ input response object using functions from+ "WebGear.Core.Trait.Status" module. -} module WebGear.Core.Trait.Body (   -- * Traits   Body (..),-  JSONBody (..),+  UnknownContentBody (..),    -- * Middlewares   requestBody,-  jsonRequestBody',-  jsonRequestBody,   respondA,-  respondJsonA,-  respondJsonA',   setBody,   setBodyWithoutContentType,-  setJSONBody,-  setJSONBodyWithoutContentType,-  setJSONBody', ) where -import Control.Arrow (ArrowChoice)+import Control.Arrow ((<<<)) import Data.Kind (Type) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8) import qualified Network.HTTP.Media as HTTP import qualified Network.HTTP.Types as HTTP-import WebGear.Core.Handler (Middleware)+import WebGear.Core.Handler (Handler (..), Middleware, unwitnessA)+import WebGear.Core.MIMETypes (MIMEType (..)) import WebGear.Core.Request (Request)-import WebGear.Core.Response (Response)-import WebGear.Core.Trait (Get, Linked, Set, Sets, Trait (..), TraitAbsence (..), plant, probe)-import WebGear.Core.Trait.Header (Header (..), RequiredHeader)+import WebGear.Core.Response (Response, ResponseBody)+import WebGear.Core.Trait (+  Get,+  Set,+  Sets,+  Trait (..),+  TraitAbsence (..),+  With (..),+  plant,+  probe,+ )+import WebGear.Core.Trait.Header (RequiredResponseHeader, ResponseHeader (..)) import WebGear.Core.Trait.Status (Status, mkResponse) --- | Request or response body with a type @t@.-newtype Body (t :: Type) = Body (Maybe HTTP.MediaType)--instance Trait (Body t) Request where-  type Attribute (Body t) Request = t--instance TraitAbsence (Body t) Request where-  type Absence (Body t) Request = Text+-- | Request or response body with MIME types @mimeTypes@ and type @t@.+newtype Body (mimeType :: Type) (t :: Type) = Body mimeType -instance Trait (Body t) Response where-  type Attribute (Body t) Response = t+instance Trait (Body mt t) Request where+  type Attribute (Body mt t) Request = t --- | A 'Trait' for converting a JSON formatted body into a value.-newtype JSONBody (t :: Type) = JSONBody (Maybe HTTP.MediaType)+instance TraitAbsence (Body mt t) Request where+  type Absence (Body mt t) Request = Text -instance Trait (JSONBody t) Request where-  type Attribute (JSONBody t) Request = t+instance Trait (Body mt t) Response where+  type Attribute (Body mt t) Response = t -instance TraitAbsence (JSONBody t) Request where-  type Absence (JSONBody t) Request = Text+-- | Type representing responses without a statically known MIME type+data UnknownContentBody = UnknownContentBody -instance Trait (JSONBody t) Response where-  type Attribute (JSONBody t) Response = t+instance Trait UnknownContentBody Response where+  type Attribute UnknownContentBody Response = ResponseBody  {- | Middleware to extract a request body. @@ -89,175 +73,89 @@   Usage: - > requestBody @t (Just "text/plain") errorHandler nextHandler+@+ requestBody \@'Text' 'PlainText' errorHandler nextHandler+@ -} requestBody ::-  forall t h req.-  (Get h (Body t) Request, ArrowChoice h) =>-  -- | Optional media type of the body-  Maybe HTTP.MediaType ->+  forall t mt h m ts.+  ( Handler h m+  , Get h (Body mt t) Request+  ) =>+  mt ->   -- | Error handler in case body cannot be retrieved-  h (Linked req Request, Text) Response ->-  Middleware h req (Body t : req)-requestBody mediaType errorHandler nextHandler = proc request -> do-  result <- probe (Body mediaType) -< request+  h (Request `With` ts, Text) Response ->+  Middleware h ts (Body mt t : ts)+requestBody mt errorHandler nextHandler = proc request -> do+  result <- probe (Body mt) -< request   case result of     Left err -> errorHandler -< (request, err)     Right t -> nextHandler -< t {-# INLINE requestBody #-} -{- | Parse the request body as JSON and convert it to a value of type-   @t@.+{- | Set the response body along with a media type. - The @nextHandler@ is invoked when the body is parsed successfully and- the @errorHandler@ is invoked when there is a parsing failure.+ The MIME type @mt@ is used to set the "Content-Type" header in the+ response.   Usage: - > jsonRequestBody @t errorHandler nextHandler--}-jsonRequestBody' ::-  forall t h req.-  (Get h (JSONBody t) Request, ArrowChoice h) =>-  -- | Optional media type of the body-  Maybe HTTP.MediaType ->-  -- | Error handler in case body cannot be retrieved-  h (Linked req Request, Text) Response ->-  Middleware h req (JSONBody t : req)-jsonRequestBody' mediaType errorHandler nextHandler = proc request -> do-  result <- probe (JSONBody mediaType) -< request-  case result of-    Left err -> errorHandler -< (request, err)-    Right t -> nextHandler -< t-{-# INLINE jsonRequestBody' #-}---- | Same as 'jsonRequestBody'' but with a media type @application/json@.-jsonRequestBody ::-  forall t h req.-  (Get h (JSONBody t) Request, ArrowChoice h) =>-  -- | error handler-  h (Linked req Request, Text) Response ->-  Middleware h req (JSONBody t : req)-jsonRequestBody = jsonRequestBody' (Just "application/json")-{-# INLINE jsonRequestBody #-}--{- | Set the response body along with a media type.--  The media type value is used to set the "Content-Type" header in the response.+@+ let body :: SomeJSONType = ...+ response' <- setBody 'JSON' -< (response, body)+@ -} setBody ::-  forall body a h ts.-  Sets h [Body body, RequiredHeader "Content-Type" Text] Response =>-  -- | The media type of the response body-  HTTP.MediaType ->-  h a (Linked ts Response) ->-  h (body, a) (Linked (RequiredHeader "Content-Type" Text : Body body : ts) Response)-setBody mediaType prevHandler = proc (body, a) -> do-  r <- prevHandler -< a-  r' <- plant (Body (Just mediaType)) -< (r, body)-  let mt = decodeUtf8 $ HTTP.renderHeader mediaType-  plant Header -< (r', mt)+  forall body mt h ts.+  ( Sets h [Body mt body, RequiredResponseHeader "Content-Type" Text] Response+  , MIMEType mt+  ) =>+  mt ->+  h (Response `With` ts, body) (Response `With` (Body mt body : RequiredResponseHeader "Content-Type" Text : ts))+setBody mt = proc (response, body) -> do+  let ct = mimeType mt+  response' <- plant ResponseHeader -< (response, decodeUtf8 $ HTTP.renderHeader ct)+  plant (Body mt) -< (response', body) {-# INLINE setBody #-} --- | Set the response body without specifying any media type.-setBodyWithoutContentType ::-  forall body a h ts.-  Set h (Body body) Response =>-  h a (Linked ts Response) ->-  h (body, a) (Linked (Body body : ts) Response)-setBodyWithoutContentType prevHandler = proc (body, a) -> do-  r <- prevHandler -< a-  plant (Body Nothing) -< (r, body)-{-# INLINE setBodyWithoutContentType #-}+{- | Set the response body without specifying any media type. -{- | Set the response body to a JSON value along with a media type.+Usage: -  The media type value is used to set the "Content-Type" header in the response.+@+ let body :: ResponseBody = ...+ response' <- setBodyWithoutContentType -< (response, body)+@ -}-setJSONBody' ::-  forall body a h ts.-  Sets h [JSONBody body, RequiredHeader "Content-Type" Text] Response =>-  -- | The media type of the response body-  HTTP.MediaType ->-  h a (Linked ts Response) ->-  h (body, a) (Linked (RequiredHeader "Content-Type" Text : JSONBody body : ts) Response)-setJSONBody' mediaType prevHandler = proc (body, a) -> do-  r <- prevHandler -< a-  r' <- plant (JSONBody (Just mediaType)) -< (r, body)-  let mt = decodeUtf8 $ HTTP.renderHeader mediaType-  plant Header -< (r', mt)-{-# INLINE setJSONBody' #-}--{- | Set the response body to a JSON value.+setBodyWithoutContentType ::+  forall h ts.+  (Set h UnknownContentBody Response) =>+  h (Response `With` ts, ResponseBody) (Response `With` (UnknownContentBody : ts))+setBodyWithoutContentType = plant UnknownContentBody+{-# INLINE setBodyWithoutContentType #-} -  The "Content-Type" header will be set to "application/json".--}-setJSONBody ::-  forall body a h ts.-  Sets h [JSONBody body, RequiredHeader "Content-Type" Text] Response =>-  h a (Linked ts Response) ->-  h (body, a) (Linked (RequiredHeader "Content-Type" Text : JSONBody body : ts) Response)-setJSONBody = setJSONBody' "application/json"-{-# INLINE setJSONBody #-}+{- | A convenience arrow to generate a response specifying a status and body. -{- | Set the response body to a JSON value without specifying any- media type.--}-setJSONBodyWithoutContentType ::-  forall body a h ts.-  Set h (JSONBody body) Response =>-  h a (Linked ts Response) ->-  h (body, a) (Linked (JSONBody body : ts) Response)-setJSONBodyWithoutContentType prevHandler = proc (body, a) -> do-  r <- prevHandler -< a-  plant (JSONBody Nothing) -< (r, body)-{-# INLINE setJSONBodyWithoutContentType #-}+ The "Content-Type" header will be set to the value specified by @mt@. -{- | A convenience arrow to generate a response specifying a status and body.+ Usage: - The "Content-Type" header will be set to the specified media type- value.+@+ let body :: SomeJSONType = ...+ respondA 'HTTP.ok200' 'JSON' -< body+@ -} respondA ::-  forall body h.-  Sets h [Status, Body body, RequiredHeader "Content-Type" Text] Response =>+  forall body mt h m.+  ( Handler h m+  , Sets h [Status, Body mt body, RequiredResponseHeader "Content-Type" Text] Response+  , MIMEType mt+  ) =>   -- | Response status   HTTP.Status ->-  -- | Media type of the response body-  HTTP.MediaType ->-  h body (Linked [RequiredHeader "Content-Type" Text, Body body, Status] Response)-respondA status mediaType = proc body ->-  setBody mediaType (mkResponse status) -< (body, ())+  mt ->+  h body Response+respondA status mt = proc body -> do+  response <- mkResponse status -< ()+  unwitnessA <<< setBody mt -< (response, body) {-# INLINE respondA #-}--{- | A convenience arrow to generate a response specifying a status and-   JSON body.-- The "Content-Type" header will be set to "application/json".--}-respondJsonA ::-  forall body h.-  Sets h [Status, JSONBody body, RequiredHeader "Content-Type" Text] Response =>-  -- | Response status-  HTTP.Status ->-  h body (Linked [RequiredHeader "Content-Type" Text, JSONBody body, Status] Response)-respondJsonA status = respondJsonA' status "application/json"-{-# INLINE respondJsonA #-}--{- | A convenience arrow to generate a response specifying a status and-   JSON body.-- The "Content-Type" header will be set to the specified media type- value.--}-respondJsonA' ::-  forall body h.-  Sets h [Status, JSONBody body, RequiredHeader "Content-Type" Text] Response =>-  -- | Response status-  HTTP.Status ->-  -- | Media type of the response body-  HTTP.MediaType ->-  h body (Linked [RequiredHeader "Content-Type" Text, JSONBody body, Status] Response)-respondJsonA' status mediaType = proc body ->-  setJSONBody' mediaType (mkResponse status) -< (body, ())-{-# INLINE respondJsonA' #-}
+ src/WebGear/Core/Trait/Cookie.hs view
@@ -0,0 +1,135 @@+-- | Traits and middlewares to handle cookies in requests and responses.+module WebGear.Core.Trait.Cookie (+  -- * Traits+  Cookie (..),+  CookieNotFound (..),+  CookieParseError (..),+  SetCookie (..),++  -- * Middlewares+  cookie,+  optionalCookie,+  setCookie,+  setOptionalCookie,+) where++import Control.Arrow (ArrowChoice)+import Data.Kind (Type)+import Data.Text (Text)+import GHC.TypeLits (Symbol)+import qualified Web.Cookie as Cookie+import WebGear.Core.Handler (Middleware)+import WebGear.Core.Modifiers (Existence (..))+import WebGear.Core.Request (Request)+import WebGear.Core.Response (Response)+import WebGear.Core.Trait (Get, Set, Trait (..), TraitAbsence (..), With, plant, probe)++-- | Indicates a missing cookie+data CookieNotFound = CookieNotFound+  deriving stock (Read, Show, Eq)++-- | Error in converting a cookie to the expected type+newtype CookieParseError = CookieParseError Text+  deriving stock (Read, Show, Eq)++-- | Trait for a cookie in HTTP requests+data Cookie (e :: Existence) (name :: Symbol) (val :: Type) = Cookie++instance Trait (Cookie Required name val) Request where+  type Attribute (Cookie Required name val) Request = val++instance TraitAbsence (Cookie Required name val) Request where+  type Absence (Cookie Required name val) Request = Either CookieNotFound CookieParseError++instance Trait (Cookie Optional name val) Request where+  type Attribute (Cookie Optional name val) Request = Maybe val++instance TraitAbsence (Cookie Optional name val) Request where+  type Absence (Cookie Optional name val) Request = CookieParseError++cookieHandler ::+  forall name val e h ts.+  (Get h (Cookie e name val) Request, ArrowChoice h) =>+  -- | error handler+  h (Request `With` ts, Absence (Cookie e name val) Request) Response ->+  Middleware h ts (Cookie e name val : ts)+cookieHandler errorHandler nextHandler = proc request -> do+  result <- probe Cookie -< request+  case result of+    Left err -> errorHandler -< (request, err)+    Right val -> nextHandler -< val+{-# INLINE cookieHandler #-}++{- | Extract a cookie and convert it to a value of type @val@.++ The associated trait attribute has type @val@.++ Example usage:++ > cookie @"name" @Integer errorHandler okHandler+-}+cookie ::+  forall name val h ts.+  (Get h (Cookie Required name val) Request, ArrowChoice h) =>+  -- | Error handler+  h (Request `With` ts, Either CookieNotFound CookieParseError) Response ->+  Middleware h ts (Cookie Required name val : ts)+cookie = cookieHandler+{-# INLINE cookie #-}++{- | Extract an optional cookie and convert it to a value of type @val@.++ The associated trait attribute has type @Maybe val@; a @Nothing@+ value indicates that the cookie is missing from the request.++ Example usage:++ > optionalCookie @"name" @Integer errorHandler okHandler+-}+optionalCookie ::+  forall name val h ts.+  (Get h (Cookie Optional name val) Request, ArrowChoice h) =>+  -- | Error handler+  h (Request `With` ts, CookieParseError) Response ->+  Middleware h ts (Cookie Optional name val : ts)+optionalCookie = cookieHandler+{-# INLINE optionalCookie #-}++-- | Trait for a cookie in HTTP responses+data SetCookie (e :: Existence) (name :: Symbol) = SetCookie++instance Trait (SetCookie Required name) Response where+  type Attribute (SetCookie Required name) Response = Cookie.SetCookie++instance Trait (SetCookie Optional name) Response where+  type Attribute (SetCookie Optional name) Response = Maybe Cookie.SetCookie++{- | Set a cookie value in a response.++ Example usage:++ > response' <- setCookie @"name" -< (response, cookie)+-}+setCookie ::+  forall name h ts.+  (Set h (SetCookie Required name) Response) =>+  h (Response `With` ts, Cookie.SetCookie) (Response `With` (SetCookie Required name : ts))+setCookie = plant SetCookie+{-# INLINE setCookie #-}++{- | Set an optional cookie value in a response.++ Setting the cookie to 'Nothing' will remove it from the response if+ it was previously set. The cookie will be considered as optional in+ all relevant places (such as documentation).++ Example usage:++ > response' <- setOptionalCookie @"name" -< (response, cookie)+-}+setOptionalCookie ::+  forall name h ts.+  (Set h (SetCookie Optional name) Response) =>+  h (Response `With` ts, Maybe Cookie.SetCookie) (Response `With` (SetCookie Optional name : ts))+setOptionalCookie = plant SetCookie+{-# INLINE setOptionalCookie #-}
src/WebGear/Core/Trait/Header.hs view
@@ -21,17 +21,20 @@  indicated in the trait attribute passed to next handler.   A response header can be set using `setHeader` or `setOptionalHeader`- arrows. They accept a linked response and a header value and sets the- header in the response. You can generate an input response object+ arrows. They accept a witnessed response and a header value and sets+ the header in the response. You can generate an input response object  using functions from "WebGear.Core.Trait.Status" module. -} module WebGear.Core.Trait.Header (   -- * Traits-  Header (..),+  RequestHeader (..),   HeaderNotFound (..),   HeaderParseError (..),-  RequiredHeader,-  OptionalHeader,+  RequiredRequestHeader,+  OptionalRequestHeader,+  ResponseHeader (..),+  RequiredResponseHeader,+  OptionalResponseHeader,    -- * Middlewares   header,@@ -51,7 +54,15 @@ import WebGear.Core.Modifiers (Existence (..), ParseStyle (..)) import WebGear.Core.Request (Request) import WebGear.Core.Response (Response)-import WebGear.Core.Trait (Get (..), Linked, Set, Trait (..), TraitAbsence (..), plant, probe)+import WebGear.Core.Trait (+  Get (..),+  Set,+  Trait (..),+  TraitAbsence (..),+  With,+  plant,+  probe,+ )  -- | Indicates a missing header data HeaderNotFound = HeaderNotFound@@ -66,46 +77,46 @@  how missing headers and parsing errors are handled. The header name  is compared case-insensitively. -}-data Header (e :: Existence) (p :: ParseStyle) (name :: Symbol) (val :: Type) = Header+data RequestHeader (e :: Existence) (p :: ParseStyle) (name :: Symbol) (val :: Type) = RequestHeader --- | A `Header` that is required and parsed strictly-type RequiredHeader = Header Required Strict+-- | A `Header` that is required in the request and parsed strictly+type RequiredRequestHeader = RequestHeader Required Strict --- | A `Header` that is optional and parsed strictly-type OptionalHeader = Header Optional Strict+-- | A `Header` that is optional in the request and parsed strictly+type OptionalRequestHeader = RequestHeader Optional Strict -instance Trait (Header Required Strict name val) Request where-  type Attribute (Header Required Strict name val) Request = val+instance Trait (RequestHeader Required Strict name val) Request where+  type Attribute (RequestHeader Required Strict name val) Request = val -instance TraitAbsence (Header Required Strict name val) Request where-  type Absence (Header Required Strict name val) Request = Either HeaderNotFound HeaderParseError+instance TraitAbsence (RequestHeader Required Strict name val) Request where+  type Absence (RequestHeader Required Strict name val) Request = Either HeaderNotFound HeaderParseError -instance Trait (Header Optional Strict name val) Request where-  type Attribute (Header Optional Strict name val) Request = Maybe val+instance Trait (RequestHeader Optional Strict name val) Request where+  type Attribute (RequestHeader Optional Strict name val) Request = Maybe val -instance TraitAbsence (Header Optional Strict name val) Request where-  type Absence (Header Optional Strict name val) Request = HeaderParseError+instance TraitAbsence (RequestHeader Optional Strict name val) Request where+  type Absence (RequestHeader Optional Strict name val) Request = HeaderParseError -instance Trait (Header Required Lenient name val) Request where-  type Attribute (Header Required Lenient name val) Request = Either Text val+instance Trait (RequestHeader Required Lenient name val) Request where+  type Attribute (RequestHeader Required Lenient name val) Request = Either Text val -instance TraitAbsence (Header Required Lenient name val) Request where-  type Absence (Header Required Lenient name val) Request = HeaderNotFound+instance TraitAbsence (RequestHeader Required Lenient name val) Request where+  type Absence (RequestHeader Required Lenient name val) Request = HeaderNotFound -instance Trait (Header Optional Lenient name val) Request where-  type Attribute (Header Optional Lenient name val) Request = Maybe (Either Text val)+instance Trait (RequestHeader Optional Lenient name val) Request where+  type Attribute (RequestHeader Optional Lenient name val) Request = Maybe (Either Text val) -instance TraitAbsence (Header Optional Lenient name val) Request where-  type Absence (Header Optional Lenient name val) Request = Void+instance TraitAbsence (RequestHeader Optional Lenient name val) Request where+  type Absence (RequestHeader Optional Lenient name val) Request = Void  headerHandler ::-  forall name val e p h req.-  (Get h (Header e p name val) Request, ArrowChoice h) =>+  forall name val e p h ts.+  (Get h (RequestHeader e p name val) Request, ArrowChoice h) =>   -- | error handler-  h (Linked req Request, Absence (Header e p name val) Request) Response ->-  Middleware h req (Header e p name val : req)+  h (Request `With` ts, Absence (RequestHeader e p name val) Request) Response ->+  Middleware h ts (RequestHeader e p name val : ts) headerHandler errorHandler nextHandler = proc request -> do-  result <- probe Header -< request+  result <- probe RequestHeader -< request   case result of     Left err -> errorHandler -< (request, err)     Right val -> nextHandler -< val@@ -120,11 +131,11 @@  > header @"Content-Length" @Integer errorHandler okHandler -} header ::-  forall name val h req.-  (Get h (Header Required Strict name val) Request, ArrowChoice h) =>+  forall name val h ts.+  (Get h (RequestHeader Required Strict name val) Request, ArrowChoice h) =>   -- | Error handler-  h (Linked req Request, Either HeaderNotFound HeaderParseError) Response ->-  Middleware h req (Header Required Strict name val : req)+  h (Request `With` ts, Either HeaderNotFound HeaderParseError) Response ->+  Middleware h ts (RequestHeader Required Strict name val : ts) header = headerHandler {-# INLINE header #-} @@ -139,11 +150,11 @@  > optionalHeader @"Content-Length" @Integer errorHandler okHandler -} optionalHeader ::-  forall name val h req.-  (Get h (Header Optional Strict name val) Request, ArrowChoice h) =>+  forall name val h ts.+  (Get h (RequestHeader Optional Strict name val) Request, ArrowChoice h) =>   -- | Error handler-  h (Linked req Request, HeaderParseError) Response ->-  Middleware h req (Header Optional Strict name val : req)+  h (Request `With` ts, HeaderParseError) Response ->+  Middleware h ts (RequestHeader Optional Strict name val : ts) optionalHeader = headerHandler {-# INLINE optionalHeader #-} @@ -158,11 +169,11 @@  > lenientHeader @"Content-Length" @Integer errorHandler okHandler -} lenientHeader ::-  forall name val h req.-  (Get h (Header Required Lenient name val) Request, ArrowChoice h) =>+  forall name val h ts.+  (Get h (RequestHeader Required Lenient name val) Request, ArrowChoice h) =>   -- | Error handler-  h (Linked req Request, HeaderNotFound) Response ->-  Middleware h req (Header Required Lenient name val : req)+  h (Request `With` ts, HeaderNotFound) Response ->+  Middleware h ts (RequestHeader Required Lenient name val : ts) lenientHeader = headerHandler {-# INLINE lenientHeader #-} @@ -177,18 +188,31 @@  > optionalLenientHeader @"Content-Length" @Integer handler -} optionalLenientHeader ::-  forall name val h req.-  (Get h (Header Optional Lenient name val) Request, ArrowChoice h) =>-  Middleware h req (Header Optional Lenient name val : req)+  forall name val h ts.+  (Get h (RequestHeader Optional Lenient name val) Request, ArrowChoice h) =>+  Middleware h ts (RequestHeader Optional Lenient name val : ts) optionalLenientHeader = headerHandler $ arr (absurd . snd) {-# INLINE optionalLenientHeader #-} -instance Trait (Header Required Strict name val) Response where-  type Attribute (Header Required Strict name val) Response = val+{- | A 'Trait' for setting a header in the HTTP response. It has a+ specified @name@ and a value of type @val@ which can be converted to+ a 'ByteString'. The header name is compared case-insensitively. The+ modifier @e@ determines whether the header is mandatory or optional.+-}+data ResponseHeader (e :: Existence) (name :: Symbol) (val :: Type) = ResponseHeader -instance Trait (Header Optional Strict name val) Response where-  type Attribute (Header Optional Strict name val) Response = Maybe val+-- | A `Header` that is required in the response+type RequiredResponseHeader = ResponseHeader Required +-- | A `Header` that is optional in the response+type OptionalResponseHeader = ResponseHeader Optional++instance Trait (ResponseHeader Required name val) Response where+  type Attribute (ResponseHeader Required name val) Response = val++instance Trait (ResponseHeader Optional name val) Response where+  type Attribute (ResponseHeader Optional name val) Response = Maybe val+ {- | Set a header value in a response.   Example usage:@@ -196,13 +220,10 @@  > response' <- setHeader @"Content-Length" -< (response, 42) -} setHeader ::-  forall name val a h res.-  Set h (Header Required Strict name val) Response =>-  h a (Linked res Response) ->-  h (val, a) (Linked (Header Required Strict name val : res) Response)-setHeader prevHandler = proc (val, a) -> do-  r <- prevHandler -< a-  plant Header -< (r, val)+  forall name val h ts.+  (Set h (ResponseHeader Required name val) Response) =>+  h (Response `With` ts, val) (Response `With` (ResponseHeader Required name val : ts))+setHeader = plant ResponseHeader {-# INLINE setHeader #-}  {- | Set an optional header value in a response.@@ -216,11 +237,8 @@  > response' <- setOptionalHeader @"Content-Length" -< (response, Just 42) -} setOptionalHeader ::-  forall name val a h res.-  Set h (Header Optional Strict name val) Response =>-  h a (Linked res Response) ->-  h (Maybe val, a) (Linked (Header Optional Strict name val : res) Response)-setOptionalHeader prevHandler = proc (val, a) -> do-  r <- prevHandler -< a-  plant Header -< (r, val)+  forall name val h ts.+  (Set h (ResponseHeader Optional name val) Response) =>+  h (Response `With` ts, Maybe val) (Response `With` (ResponseHeader Optional name val : ts))+setOptionalHeader = plant ResponseHeader {-# INLINE setOptionalHeader #-}
src/WebGear/Core/Trait/Method.hs view
@@ -43,6 +43,6 @@ method ::   (Get h Method Request, ArrowChoice h, ArrowError RouteMismatch h) =>   HTTP.StdMethod ->-  Middleware h req (Method : req)+  Middleware h ts (Method : ts) method m nextHandler = probe (Method m) >>> routeMismatch ||| nextHandler {-# INLINE method #-}
src/WebGear/Core/Trait/Path.hs view
@@ -83,7 +83,7 @@ path ::   (Get h Path Request, ArrowChoice h, ArrowError RouteMismatch h) =>   Text ->-  Middleware h req (Path : req)+  Middleware h ts (Path : ts) path s nextHandler = probe (Path s) >>> routeMismatch ||| nextHandler {-# INLINE path #-} @@ -100,16 +100,16 @@  > pathVar @"objId" @Int handler -} pathVar ::-  forall tag val h req.+  forall tag val h ts.   (Get h (PathVar tag val) Request, ArrowChoice h, ArrowError RouteMismatch h) =>-  Middleware h req (PathVar tag val : req)+  Middleware h ts (PathVar tag val : ts) pathVar nextHandler = probe PathVar >>> routeMismatch ||| nextHandler {-# INLINE pathVar #-}  -- | A middleware that verifies that end of path is reached. pathEnd ::   (Get h PathEnd Request, ArrowChoice h, ArrowError RouteMismatch h) =>-  Middleware h req (PathEnd : req)+  Middleware h ts (PathEnd : ts) pathEnd nextHandler = probe PathEnd >>> routeMismatch ||| nextHandler {-# INLINE pathEnd #-} @@ -212,7 +212,7 @@ compose :: Exp -> Exp -> Exp compose l = UInfixE l (VarE $ mkName ".") -splitOn :: Eq a => a -> [a] -> NonEmpty [a]+splitOn :: (Eq a) => a -> [a] -> NonEmpty [a] splitOn sep = foldr f ([] :| [])   where     f x acc | x == sep = [] :| toList acc
src/WebGear/Core/Trait/QueryParam.hs view
@@ -43,7 +43,7 @@ import WebGear.Core.Modifiers (Existence (..), ParseStyle (..)) import WebGear.Core.Request (Request) import WebGear.Core.Response (Response)-import WebGear.Core.Trait (Get, Linked, Trait (..), TraitAbsence (..), probe)+import WebGear.Core.Trait (Get, Trait (..), TraitAbsence (..), With, probe)  {- | Capture a query parameter with a specified @name@ and convert it to  a value of type @val@. The type parameter @e@ denotes whether the@@ -92,10 +92,10 @@   type Absence (QueryParam Optional Lenient name val) Request = Void  queryParamHandler ::-  forall name val e p h req.+  forall name val e p h ts.   (Get h (QueryParam e p name val) Request, ArrowChoice h) =>-  h (Linked req Request, Absence (QueryParam e p name val) Request) Response ->-  Middleware h req (QueryParam e p name val : req)+  h (Request `With` ts, Absence (QueryParam e p name val) Request) Response ->+  Middleware h ts (QueryParam e p name val : ts) queryParamHandler errorHandler nextHandler = proc request -> do   result <- probe QueryParam -< request   case result of@@ -113,10 +113,10 @@  > queryParam @"limit" @Integer errorHandler okHandler -} queryParam ::-  forall name val h req.+  forall name val h ts.   (Get h (QueryParam Required Strict name val) Request, ArrowChoice h) =>-  h (Linked req Request, Either ParamNotFound ParamParseError) Response ->-  Middleware h req (QueryParam Required Strict name val : req)+  h (Request `With` ts, Either ParamNotFound ParamParseError) Response ->+  Middleware h ts (QueryParam Required Strict name val : ts) queryParam = queryParamHandler {-# INLINE queryParam #-} @@ -131,10 +131,10 @@  > optionalQueryParam @"limit" @Integer errorHandler okHandler -} optionalQueryParam ::-  forall name val h req.+  forall name val h ts.   (Get h (QueryParam Optional Strict name val) Request, ArrowChoice h) =>-  h (Linked req Request, ParamParseError) Response ->-  Middleware h req (QueryParam Optional Strict name val : req)+  h (Request `With` ts, ParamParseError) Response ->+  Middleware h ts (QueryParam Optional Strict name val : ts) optionalQueryParam = queryParamHandler {-# INLINE optionalQueryParam #-} @@ -149,10 +149,10 @@  > lenientQueryParam @"limit" @Integer errorHandler okHandler -} lenientQueryParam ::-  forall name val h req.+  forall name val h ts.   (Get h (QueryParam Required Lenient name val) Request, ArrowChoice h) =>-  h (Linked req Request, ParamNotFound) Response ->-  Middleware h req (QueryParam Required Lenient name val : req)+  h (Request `With` ts, ParamNotFound) Response ->+  Middleware h ts (QueryParam Required Lenient name val : ts) lenientQueryParam = queryParamHandler {-# INLINE lenientQueryParam #-} @@ -167,8 +167,8 @@  missing query parameters are reported in the trait attribute. -} optionalLenientQueryParam ::-  forall name val h req.+  forall name val h ts.   (Get h (QueryParam Optional Lenient name val) Request, ArrowChoice h) =>-  Middleware h req (QueryParam Optional Lenient name val : req)+  Middleware h ts (QueryParam Optional Lenient name val : ts) optionalLenientQueryParam = queryParamHandler $ arr (absurd . snd) {-# INLINE optionalLenientQueryParam #-}
src/WebGear/Core/Trait/Status.hs view
@@ -53,8 +53,8 @@ ) where  import qualified Network.HTTP.Types as HTTP-import WebGear.Core.Response (Response (..))-import WebGear.Core.Trait (Linked, Set, Trait (..), linkzero, plant)+import WebGear.Core.Response (Response (..), ResponseBody (ResponseBodyBuilder))+import WebGear.Core.Trait (Set, Trait (..), With, plant, wzero)  -- | HTTP response status newtype Status = Status HTTP.Status@@ -63,238 +63,238 @@   type Attribute Status Response = HTTP.Status  -- | Generate a response with the specified status-mkResponse :: Set h Status Response => HTTP.Status -> h () (Linked '[Status] Response)+mkResponse :: (Set h Status Response) => HTTP.Status -> h () (Response `With` '[Status]) mkResponse status = proc () -> do-  let response = linkzero $ Response status [] Nothing+  let response = wzero $ Response status [] (ResponseBodyBuilder mempty)   plant (Status status) -< (response, status) {-# INLINE mkResponse #-}  -- | Continue 100 response-continue100 :: Set h Status Response => h () (Linked '[Status] Response)+continue100 :: (Set h Status Response) => h () (Response `With` '[Status]) continue100 = mkResponse HTTP.continue100 {-# INLINE continue100 #-}  -- | Switching Protocols 101 response-switchingProtocols101 :: Set h Status Response => h () (Linked '[Status] Response)+switchingProtocols101 :: (Set h Status Response) => h () (Response `With` '[Status]) switchingProtocols101 = mkResponse HTTP.switchingProtocols101 {-# INLINE switchingProtocols101 #-}  -- | OK 200 response-ok200 :: Set h Status Response => h () (Linked '[Status] Response)+ok200 :: (Set h Status Response) => h () (Response `With` '[Status]) ok200 = mkResponse HTTP.ok200 {-# INLINE ok200 #-}  -- | Created 201 response-created201 :: Set h Status Response => h () (Linked '[Status] Response)+created201 :: (Set h Status Response) => h () (Response `With` '[Status]) created201 = mkResponse HTTP.created201 {-# INLINE created201 #-}  -- | Accepted 202 response-accepted202 :: Set h Status Response => h () (Linked '[Status] Response)+accepted202 :: (Set h Status Response) => h () (Response `With` '[Status]) accepted202 = mkResponse HTTP.accepted202 {-# INLINE accepted202 #-}  -- | Non-Authoritative 203 response-nonAuthoritative203 :: Set h Status Response => h () (Linked '[Status] Response)+nonAuthoritative203 :: (Set h Status Response) => h () (Response `With` '[Status]) nonAuthoritative203 = mkResponse HTTP.nonAuthoritative203 {-# INLINE nonAuthoritative203 #-}  -- | No Content 204 response-noContent204 :: Set h Status Response => h () (Linked '[Status] Response)+noContent204 :: (Set h Status Response) => h () (Response `With` '[Status]) noContent204 = mkResponse HTTP.noContent204 {-# INLINE noContent204 #-}  -- | Reset Content 205 response-resetContent205 :: Set h Status Response => h () (Linked '[Status] Response)+resetContent205 :: (Set h Status Response) => h () (Response `With` '[Status]) resetContent205 = mkResponse HTTP.resetContent205 {-# INLINE resetContent205 #-}  -- | Partial Content 206 response-partialContent206 :: Set h Status Response => h () (Linked '[Status] Response)+partialContent206 :: (Set h Status Response) => h () (Response `With` '[Status]) partialContent206 = mkResponse HTTP.partialContent206 {-# INLINE partialContent206 #-}  -- | Multiple Choices 300 response-multipleChoices300 :: Set h Status Response => h () (Linked '[Status] Response)+multipleChoices300 :: (Set h Status Response) => h () (Response `With` '[Status]) multipleChoices300 = mkResponse HTTP.multipleChoices300 {-# INLINE multipleChoices300 #-}  -- | Moved Permanently 301 response-movedPermanently301 :: Set h Status Response => h () (Linked '[Status] Response)+movedPermanently301 :: (Set h Status Response) => h () (Response `With` '[Status]) movedPermanently301 = mkResponse HTTP.movedPermanently301 {-# INLINE movedPermanently301 #-}  -- | Found 302 response-found302 :: Set h Status Response => h () (Linked '[Status] Response)+found302 :: (Set h Status Response) => h () (Response `With` '[Status]) found302 = mkResponse HTTP.found302 {-# INLINE found302 #-}  -- | See Other 303 response-seeOther303 :: Set h Status Response => h () (Linked '[Status] Response)+seeOther303 :: (Set h Status Response) => h () (Response `With` '[Status]) seeOther303 = mkResponse HTTP.seeOther303 {-# INLINE seeOther303 #-}  -- | Not Modified 304 response-notModified304 :: Set h Status Response => h () (Linked '[Status] Response)+notModified304 :: (Set h Status Response) => h () (Response `With` '[Status]) notModified304 = mkResponse HTTP.notModified304 {-# INLINE notModified304 #-}  -- | Temporary Redirect 307 response-temporaryRedirect307 :: Set h Status Response => h () (Linked '[Status] Response)+temporaryRedirect307 :: (Set h Status Response) => h () (Response `With` '[Status]) temporaryRedirect307 = mkResponse HTTP.temporaryRedirect307 {-# INLINE temporaryRedirect307 #-}  -- | Permanent Redirect 308 response-permanentRedirect308 :: Set h Status Response => h () (Linked '[Status] Response)+permanentRedirect308 :: (Set h Status Response) => h () (Response `With` '[Status]) permanentRedirect308 = mkResponse HTTP.permanentRedirect308 {-# INLINE permanentRedirect308 #-}  -- | Bad Request 400 response-badRequest400 :: Set h Status Response => h () (Linked '[Status] Response)+badRequest400 :: (Set h Status Response) => h () (Response `With` '[Status]) badRequest400 = mkResponse HTTP.badRequest400 {-# INLINE badRequest400 #-}  -- | Unauthorized 401 response-unauthorized401 :: Set h Status Response => h () (Linked '[Status] Response)+unauthorized401 :: (Set h Status Response) => h () (Response `With` '[Status]) unauthorized401 = mkResponse HTTP.unauthorized401 {-# INLINE unauthorized401 #-}  -- | Payment Required 402 response-paymentRequired402 :: Set h Status Response => h () (Linked '[Status] Response)+paymentRequired402 :: (Set h Status Response) => h () (Response `With` '[Status]) paymentRequired402 = mkResponse HTTP.paymentRequired402 {-# INLINE paymentRequired402 #-}  -- | Forbidden 403 response-forbidden403 :: Set h Status Response => h () (Linked '[Status] Response)+forbidden403 :: (Set h Status Response) => h () (Response `With` '[Status]) forbidden403 = mkResponse HTTP.forbidden403 {-# INLINE forbidden403 #-}  -- | Not Found 404 response-notFound404 :: Set h Status Response => h () (Linked '[Status] Response)+notFound404 :: (Set h Status Response) => h () (Response `With` '[Status]) notFound404 = mkResponse HTTP.notFound404 {-# INLINE notFound404 #-}  -- | Method Not Allowed 405 response-methodNotAllowed405 :: Set h Status Response => h () (Linked '[Status] Response)+methodNotAllowed405 :: (Set h Status Response) => h () (Response `With` '[Status]) methodNotAllowed405 = mkResponse HTTP.methodNotAllowed405 {-# INLINE methodNotAllowed405 #-}  -- | Not Acceptable 406 response-notAcceptable406 :: Set h Status Response => h () (Linked '[Status] Response)+notAcceptable406 :: (Set h Status Response) => h () (Response `With` '[Status]) notAcceptable406 = mkResponse HTTP.notAcceptable406 {-# INLINE notAcceptable406 #-}  -- | Proxy Authentication Required 407 response-proxyAuthenticationRequired407 :: Set h Status Response => h () (Linked '[Status] Response)+proxyAuthenticationRequired407 :: (Set h Status Response) => h () (Response `With` '[Status]) proxyAuthenticationRequired407 = mkResponse HTTP.proxyAuthenticationRequired407 {-# INLINE proxyAuthenticationRequired407 #-}  -- | Request Timeout 408 response-requestTimeout408 :: Set h Status Response => h () (Linked '[Status] Response)+requestTimeout408 :: (Set h Status Response) => h () (Response `With` '[Status]) requestTimeout408 = mkResponse HTTP.requestTimeout408 {-# INLINE requestTimeout408 #-}  -- | Conflict 409 response-conflict409 :: Set h Status Response => h () (Linked '[Status] Response)+conflict409 :: (Set h Status Response) => h () (Response `With` '[Status]) conflict409 = mkResponse HTTP.conflict409 {-# INLINE conflict409 #-}  -- | Gone 410 response-gone410 :: Set h Status Response => h () (Linked '[Status] Response)+gone410 :: (Set h Status Response) => h () (Response `With` '[Status]) gone410 = mkResponse HTTP.gone410 {-# INLINE gone410 #-}  -- | Length Required 411 response-lengthRequired411 :: Set h Status Response => h () (Linked '[Status] Response)+lengthRequired411 :: (Set h Status Response) => h () (Response `With` '[Status]) lengthRequired411 = mkResponse HTTP.lengthRequired411 {-# INLINE lengthRequired411 #-}  -- | Precondition Failed 412 response-preconditionFailed412 :: Set h Status Response => h () (Linked '[Status] Response)+preconditionFailed412 :: (Set h Status Response) => h () (Response `With` '[Status]) preconditionFailed412 = mkResponse HTTP.preconditionFailed412 {-# INLINE preconditionFailed412 #-}  -- | Request Entity Too Large 413 response-requestEntityTooLarge413 :: Set h Status Response => h () (Linked '[Status] Response)+requestEntityTooLarge413 :: (Set h Status Response) => h () (Response `With` '[Status]) requestEntityTooLarge413 = mkResponse HTTP.requestEntityTooLarge413 {-# INLINE requestEntityTooLarge413 #-}  -- | Request URI Too Long 414 response-requestURITooLong414 :: Set h Status Response => h () (Linked '[Status] Response)+requestURITooLong414 :: (Set h Status Response) => h () (Response `With` '[Status]) requestURITooLong414 = mkResponse HTTP.requestURITooLong414 {-# INLINE requestURITooLong414 #-}  -- | Unsupported Media Type 415 response-unsupportedMediaType415 :: Set h Status Response => h () (Linked '[Status] Response)+unsupportedMediaType415 :: (Set h Status Response) => h () (Response `With` '[Status]) unsupportedMediaType415 = mkResponse HTTP.unsupportedMediaType415 {-# INLINE unsupportedMediaType415 #-}  -- | Requested Range Not Satisfiable 416 response-requestedRangeNotSatisfiable416 :: Set h Status Response => h () (Linked '[Status] Response)+requestedRangeNotSatisfiable416 :: (Set h Status Response) => h () (Response `With` '[Status]) requestedRangeNotSatisfiable416 = mkResponse HTTP.requestedRangeNotSatisfiable416 {-# INLINE requestedRangeNotSatisfiable416 #-}  -- | Expectation Failed 417 response-expectationFailed417 :: Set h Status Response => h () (Linked '[Status] Response)+expectationFailed417 :: (Set h Status Response) => h () (Response `With` '[Status]) expectationFailed417 = mkResponse HTTP.expectationFailed417 {-# INLINE expectationFailed417 #-}  -- | I'm A Teapot 418 response-imATeapot418 :: Set h Status Response => h () (Linked '[Status] Response)+imATeapot418 :: (Set h Status Response) => h () (Response `With` '[Status]) imATeapot418 = mkResponse HTTP.imATeapot418 {-# INLINE imATeapot418 #-}  -- | Unprocessable Entity 422 response-unprocessableEntity422 :: Set h Status Response => h () (Linked '[Status] Response)+unprocessableEntity422 :: (Set h Status Response) => h () (Response `With` '[Status]) unprocessableEntity422 = mkResponse HTTP.unprocessableEntity422 {-# INLINE unprocessableEntity422 #-}  -- | Precondition Required 428 response-preconditionRequired428 :: Set h Status Response => h () (Linked '[Status] Response)+preconditionRequired428 :: (Set h Status Response) => h () (Response `With` '[Status]) preconditionRequired428 = mkResponse HTTP.preconditionRequired428 {-# INLINE preconditionRequired428 #-}  -- | Too Many Requests 429 response-tooManyRequests429 :: Set h Status Response => h () (Linked '[Status] Response)+tooManyRequests429 :: (Set h Status Response) => h () (Response `With` '[Status]) tooManyRequests429 = mkResponse HTTP.tooManyRequests429 {-# INLINE tooManyRequests429 #-}  -- | Request Header Fields Too Large 431 response-requestHeaderFieldsTooLarge431 :: Set h Status Response => h () (Linked '[Status] Response)+requestHeaderFieldsTooLarge431 :: (Set h Status Response) => h () (Response `With` '[Status]) requestHeaderFieldsTooLarge431 = mkResponse HTTP.requestHeaderFieldsTooLarge431 {-# INLINE requestHeaderFieldsTooLarge431 #-}  -- | Internal Server Error 500 response-internalServerError500 :: Set h Status Response => h () (Linked '[Status] Response)+internalServerError500 :: (Set h Status Response) => h () (Response `With` '[Status]) internalServerError500 = mkResponse HTTP.internalServerError500 {-# INLINE internalServerError500 #-}  -- | Not Implemented 501 response-notImplemented501 :: Set h Status Response => h () (Linked '[Status] Response)+notImplemented501 :: (Set h Status Response) => h () (Response `With` '[Status]) notImplemented501 = mkResponse HTTP.notImplemented501 {-# INLINE notImplemented501 #-}  -- | Bad Gateway 502 response-badGateway502 :: Set h Status Response => h () (Linked '[Status] Response)+badGateway502 :: (Set h Status Response) => h () (Response `With` '[Status]) badGateway502 = mkResponse HTTP.badGateway502 {-# INLINE badGateway502 #-}  -- | Service Unavailable 503 response-serviceUnavailable503 :: Set h Status Response => h () (Linked '[Status] Response)+serviceUnavailable503 :: (Set h Status Response) => h () (Response `With` '[Status]) serviceUnavailable503 = mkResponse HTTP.serviceUnavailable503 {-# INLINE serviceUnavailable503 #-}  -- | Gateway Timeout 504 response-gatewayTimeout504 :: Set h Status Response => h () (Linked '[Status] Response)+gatewayTimeout504 :: (Set h Status Response) => h () (Response `With` '[Status]) gatewayTimeout504 = mkResponse HTTP.gatewayTimeout504 {-# INLINE gatewayTimeout504 #-}  -- | HTTP Version Not Supported 505 response-httpVersionNotSupported505 :: Set h Status Response => h () (Linked '[Status] Response)+httpVersionNotSupported505 :: (Set h Status Response) => h () (Response `With` '[Status]) httpVersionNotSupported505 = mkResponse HTTP.httpVersionNotSupported505 {-# INLINE httpVersionNotSupported505 #-}  -- | Network Authentication Required 511 response-networkAuthenticationRequired511 :: Set h Status Response => h () (Linked '[Status] Response)+networkAuthenticationRequired511 :: (Set h Status Response) => h () (Response `With` '[Status]) networkAuthenticationRequired511 = mkResponse HTTP.networkAuthenticationRequired511 {-# INLINE networkAuthenticationRequired511 #-}
src/WebGear/Core/Traits.hs view
@@ -4,6 +4,7 @@   module WebGear.Core.Trait.Auth.JWT,   module WebGear.Core.Trait.Auth.Common,   module WebGear.Core.Trait.Body,+  module WebGear.Core.Trait.Cookie,   module WebGear.Core.Trait.Header,   module WebGear.Core.Trait.Method,   module WebGear.Core.Trait.Path,@@ -12,7 +13,10 @@   StdHandler, ) where +import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText import WebGear.Core.Handler (Handler)+import WebGear.Core.MIMETypes (PlainText) import WebGear.Core.Request (Request) import WebGear.Core.Response (Response) import WebGear.Core.Trait (Gets, Sets)@@ -20,24 +24,29 @@ import WebGear.Core.Trait.Auth.Common import WebGear.Core.Trait.Auth.JWT import WebGear.Core.Trait.Body+import WebGear.Core.Trait.Cookie import WebGear.Core.Trait.Header import WebGear.Core.Trait.Method import WebGear.Core.Trait.Path import WebGear.Core.Trait.QueryParam import WebGear.Core.Trait.Status -{- | Constraints that include all common traits.+{- | Constraints that include a set of common traits for handlers.   The type variables are:   * @h@ - The handler arrow  * @m@ - The underlying monad of the handler- * @req@ - List of traits the handler `Gets` from the request- * @res@ - List of traits the handler `Sets` on the response -}-type StdHandler h m req res =+type StdHandler h m =   ( Handler h m   , Gets h [Method, Path, PathEnd] Request-  , Gets h req Request-  , Sets h (Status : res) Response+  , Sets+      h+      '[ Status+       , Body PlainText String+       , Body PlainText Text.Text+       , Body PlainText LText.Text+       ]+      Response   )
webgear-core.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.4  name:                webgear-core-version:             1.0.5+version:             1.1.0 synopsis:            Composable, type-safe library to build HTTP APIs description:         WebGear is a library to for building composable, type-safe HTTP APIs.@@ -54,19 +54,20 @@                       TypeFamilies                       TypeOperators   build-depends:      base >=4.13.0.0 && <4.19-                    , bytestring >=0.10.10.1 && <0.12+                    , binary >= 0.8.0.0 && <0.9+                    , bytestring >=0.10.10.1 && <0.13                     , case-insensitive ==1.2.*+                    , cookie >=0.4.5 && <0.5                     , filepath ==1.4.*-                    , http-api-data >=0.4.2 && <0.6+                    , http-api-data >=0.4.2 && <0.7                     , http-media ==0.8.*                     , http-types ==0.12.*                     , network ==3.1.*-                    , safe-exceptions ==0.1.*                     , tagged ==0.8.*                     , template-haskell >=2.15.0.0 && <2.21-                    , text >=1.2.0.0 && <2.1-                    , unordered-containers ==0.2.*+                    , text >=1.2.0.0 && <2.2                     , wai ==3.2.*+                    , wai-extra ==3.1.*   ghc-options:        -Wall                       -Wno-unticked-promoted-constructors                       -Wcompat@@ -91,11 +92,13 @@                     , WebGear.Core.Request                     , WebGear.Core.Response                     , WebGear.Core.Handler+                    , WebGear.Core.MIMETypes                     , WebGear.Core.Traits                     , WebGear.Core.Trait.Auth.Basic                     , WebGear.Core.Trait.Auth.JWT                     , WebGear.Core.Trait.Auth.Common                     , WebGear.Core.Trait.Body+                    , WebGear.Core.Trait.Cookie                     , WebGear.Core.Trait.Header                     , WebGear.Core.Trait.Method                     , WebGear.Core.Trait.Path