symantic-http-server 0.0.0.20190324 → 0.1.1.20190410
raw patch · 2 files changed
+361/−49 lines, 2 filesdep ~base64-bytestringdep ~symantic-httpPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base64-bytestring, symantic-http
API changes (from Hackage documentation)
- Symantic.HTTP.Server: instance GHC.Base.Functor (Symantic.HTTP.Server.Server responses)
+ Symantic.HTTP.Server: BinTree0 :: a -> BinTree a
+ Symantic.HTTP.Server: BinTree2 :: BinTree a -> BinTree a -> BinTree a
+ Symantic.HTTP.Server: [Captures0] :: PathConstraint Server a => Proxy a -> Name -> repr x k -> Captures repr ( 'BinTree0 (a -> x)) k
+ Symantic.HTTP.Server: [Captures2] :: Captures repr x k -> Captures repr y k -> Captures repr ( 'BinTree2 x y) k
+ Symantic.HTTP.Server: [Router_Alt] :: Router repr a k -> Router repr b k -> Router repr (a :!: b) k
+ Symantic.HTTP.Server: [Router_Any] :: repr a b -> Router repr a b
+ Symantic.HTTP.Server: [Router_Cap] :: PathConstraint Server a => Name -> Router repr (a -> k) k
+ Symantic.HTTP.Server: [Router_Caps] :: Captures (Router repr) cs k -> Router repr (AltFromBinTree cs) k
+ Symantic.HTTP.Server: [Router_Cat] :: Router repr a b -> Router repr b c -> Router repr a c
+ Symantic.HTTP.Server: [Router_Map] :: Map PathSegment (Router repr a k) -> Router repr a k
+ Symantic.HTTP.Server: [Router_Seg] :: PathSegment -> Router repr k k
+ Symantic.HTTP.Server: [Router_Union] :: (b -> a) -> Router repr a k -> Router repr b k
+ Symantic.HTTP.Server: captures :: (HTTP_Routing repr, Trans repr) => HTTP_Routing (UnTrans repr) => Captures repr cs k -> repr (AltFromBinTree cs) k
+ Symantic.HTTP.Server: class HTTP_Routing repr
+ Symantic.HTTP.Server: data BinTree a
+ Symantic.HTTP.Server: data Captures repr (cs :: BinTree Type) k
+ Symantic.HTTP.Server: data Router repr a b
+ Symantic.HTTP.Server: instance (repr Data.Type.Equality.~ Symantic.HTTP.Server.Server) => Symantic.HTTP.API.HTTP_Path (Symantic.HTTP.Server.Router repr)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.Alt (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.Cat (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_BasicAuth (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_Body (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_BodyStream (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_Header (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_Query (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_Raw (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_Raw Symantic.HTTP.Server.Server
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_Response (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.HTTP_ResponseStream (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.Pro (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.API.Trans (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.Server.HTTP_Routing (Symantic.HTTP.Server.Router Symantic.HTTP.Server.Server)
+ Symantic.HTTP.Server: instance Symantic.HTTP.Server.HTTP_Routing Symantic.HTTP.Server.Server
+ Symantic.HTTP.Server: router :: Router repr a b -> Router repr a b
+ Symantic.HTTP.Server: router_Alt :: Router repr a k -> Router repr b k -> Router repr (a :!: b) k
+ Symantic.HTTP.Server: router_Map :: Map PathSegment (Router repr a k) -> Map PathSegment (Router repr b k) -> Router repr (a :!: b) k
+ Symantic.HTTP.Server: routing :: (HTTP_Routing repr, Trans repr) => HTTP_Routing (UnTrans repr) => Map PathSegment (repr a k) -> repr a k
+ Symantic.HTTP.Server: type family AltFromBinTree (cs :: BinTree Type) :: Type
- Symantic.HTTP.Server: Server :: StateT ServerState (ServerCheckT [ServerErrorBody] (ServerCheckT [ServerErrorHeader] (ServerCheckT [ServerErrorQuery] (ServerCheckT [ServerErrorContentType] (ServerCheckT [ServerErrorAccept] (ServerCheckT [ServerErrorBasicAuth] (ServerCheckT [ServerErrorMethod] (ServerCheckT [ServerErrorPath] IO)))))))) (responses -> k) -> Server responses k
+ Symantic.HTTP.Server: Server :: StateT ServerState (ServerCheckT [ServerErrorBody] (ServerCheckT [ServerErrorHeader] (ServerCheckT [ServerErrorQuery] (ServerCheckT [ServerErrorContentType] (ServerCheckT [ServerErrorAccept] (ServerCheckT [ServerErrorBasicAuth] (ServerCheckT [ServerErrorMethod] (ServerCheckT [ServerErrorPath] IO)))))))) (handlers -> k) -> Server handlers k
- Symantic.HTTP.Server: [unServer] :: Server responses k -> StateT ServerState (ServerCheckT [ServerErrorBody] (ServerCheckT [ServerErrorHeader] (ServerCheckT [ServerErrorQuery] (ServerCheckT [ServerErrorContentType] (ServerCheckT [ServerErrorAccept] (ServerCheckT [ServerErrorBasicAuth] (ServerCheckT [ServerErrorMethod] (ServerCheckT [ServerErrorPath] IO)))))))) (responses -> k)
+ Symantic.HTTP.Server: [unServer] :: Server handlers k -> StateT ServerState (ServerCheckT [ServerErrorBody] (ServerCheckT [ServerErrorHeader] (ServerCheckT [ServerErrorQuery] (ServerCheckT [ServerErrorContentType] (ServerCheckT [ServerErrorAccept] (ServerCheckT [ServerErrorBasicAuth] (ServerCheckT [ServerErrorMethod] (ServerCheckT [ServerErrorPath] IO)))))))) (handlers -> k)
- Symantic.HTTP.Server: newtype Server responses k
+ Symantic.HTTP.Server: newtype Server handlers k
- Symantic.HTTP.Server: newtype ServerBodyArg (ts :: [*]) a
+ Symantic.HTTP.Server: newtype ServerBodyArg (ts :: [Type]) a
- Symantic.HTTP.Server: newtype ServerBodyStreamArg as (ts :: [*]) framing
+ Symantic.HTTP.Server: newtype ServerBodyStreamArg as (ts :: [Type]) framing
- Symantic.HTTP.Server: newtype ServerRes (ts :: [*]) m a
+ Symantic.HTTP.Server: newtype ServerRes (ts :: [Type]) m a
- Symantic.HTTP.Server: newtype ServerResStream framing (ts :: [*]) m as
+ Symantic.HTTP.Server: newtype ServerResStream framing (ts :: [Type]) m as
- Symantic.HTTP.Server: server :: Server responses (Response Server) -> responses -> Application
+ Symantic.HTTP.Server: server :: Router Server handlers (Response Server) -> handlers -> Application
Files
- Symantic/HTTP/Server.hs +357/−45
- symantic-http-server.cabal +4/−4
Symantic/HTTP/Server.hs view
@@ -1,26 +1,28 @@-{-# LANGUAGE GADTs #-}+{-# LANGUAGE GADTs #-} -- for 'Router' and 'RouterUnion' {-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DataKinds #-} -- for 'BinTree'+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE UndecidableInstances #-} -- for nested type family application,+ -- eg. in 'BodyStreamConstraint' {-# OPTIONS_GHC -fno-warn-orphans #-} -- | See <https://hackage.haskell.org/package/symantic-http-demo symantic-http-demo> -- for an example of how to use this module. module Symantic.HTTP.Server where -import Control.Arrow (first) import Control.Applicative (Applicative(..))+import Control.Arrow (first) import Control.Monad (Monad(..), unless, sequence, guard, (=<<)) import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Trans.Except (ExceptT(..), runExceptT) import Data.Bool import Data.Either (Either(..)) import Data.Eq (Eq(..))-import Data.Function (($), (.), id)+import Data.Function (($), (.), id, const) import Data.Functor (Functor(..), (<$>)) import Data.Int (Int)+import Data.Kind (Type) import Data.Maybe (Maybe(..), fromMaybe, catMaybes) import Data.Monoid (Monoid(..)) import Data.Ord (Ord(..))@@ -41,6 +43,9 @@ import qualified Data.ByteString.Lazy as BSL import qualified Data.List as List import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map.Merge.Strict as Map+import qualified Data.Map.Strict as Map+import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import qualified Data.Word8 as Word8 import qualified Network.HTTP.Media as Media@@ -52,16 +57,16 @@ import Symantic.HTTP -- * Type 'Server'--- | @'Server' responses k@ is a recipe to produce an 'Wai.Application'--- from arguments 'responses' (one per number of alternative routes),+-- | (@'Server' handlers k@) is a recipe to produce an 'Wai.Application'+-- from given ('handlers') (one per number of alternative routes), -- separated by (':!:'). ----- 'Server' is analogous to a scanf using a format customized for HTTP routing.+-- 'Server' is analogous to a scanf using the API as a format customized for HTTP routing. -- -- The multiple 'ServerCheckT' monad transformers are there -- to prioritize the errors according to the type of check raising them, -- instead of the order of the combinators within an actual API specification.-newtype Server responses k = Server { unServer ::+newtype Server handlers k = Server { unServer :: S.StateT ServerState (ServerCheckT [ServerErrorBody] -- 8th check, 400 error (ServerCheckT [ServerErrorHeader] -- 7th check, 400 error@@ -72,17 +77,17 @@ (ServerCheckT [ServerErrorMethod] -- 2nd check, 405 error (ServerCheckT [ServerErrorPath] -- 1st check, 404 error IO))))))))- (responses -> k)- } deriving (Functor)+ (handlers -> k)+ } --- | @'server' api responses@ returns a 'Wai.Application'+-- | (@'server' api handlers@) returns an 'Wai.Application' -- ready to be given to @Warp.run 80@. server ::- Server responses (Response Server) ->- responses ->+ Router Server handlers (Response Server) ->+ handlers -> Wai.Application-server (Server api) responses rq re = do- lrPath <- runServerChecks api $ ServerState rq+server api handlers rq re = do+ lrPath <- runServerChecks (unServer $ unTrans $ router api) $ ServerState rq case lrPath of Left err -> respondError HTTP.status404 [] err Right lrMethod ->@@ -117,8 +122,8 @@ Right lrBody -> case lrBody of Left err -> respondError HTTP.status400 [] err- Right (app, _st) ->- app responses rq re+ Right (app, st) ->+ app handlers (serverState_request st) re where respondError :: Show err =>@@ -173,7 +178,7 @@ -- ** Type 'ServerState' newtype ServerState = ServerState { serverState_request :: Wai.Request- }+ } -- deriving (Show) instance Show ServerState where show _ = "ServerState" @@ -307,16 +312,17 @@ Left ye -> MC.throw ye Right _yBody -> MC.throw xe Right (a2b, st') ->- (first (. a2b)) <$> S.runStateT y st'+ first (. a2b) <$> S.runStateT y st' instance Alt Server where+ -- (<!>) :: repr a k -> repr b k -> repr (a:!:b) k Server x <!> Server y = Server $ S.StateT $ \st -> do xPath <- MC.exec @IO $ runServerChecks x st- yPath <- MC.exec @IO $ runServerChecks y st let fy = (first (\b2k (_a:!:b) -> b2k b) <$>) case xPath of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left ye -> MC.throw (xe<>ye) Right yMethod ->@@ -325,7 +331,8 @@ Right xMethod -> case xMethod of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left _ye -> MC.throw xe Right yMethod ->@@ -337,7 +344,8 @@ Right xBasicAuth -> case xBasicAuth of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left _ye -> MC.throw xe Right yMethod ->@@ -352,7 +360,8 @@ Right xAccept -> case xAccept of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left _ye -> MC.throw xe Right yMethod ->@@ -370,7 +379,8 @@ Right xContentType -> case xContentType of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left _ye -> MC.throw xe Right yMethod ->@@ -391,7 +401,8 @@ Right xQuery -> case xQuery of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left _ye -> MC.throw xe Right yMethod ->@@ -415,7 +426,8 @@ Right xHeader -> case xHeader of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left _ye -> MC.throw xe Right yMethod ->@@ -442,7 +454,8 @@ Right xBody -> case xBody of Left xe | FailFatal{} <- xe -> MC.throw xe- | otherwise ->+ | otherwise -> do+ yPath <- MC.exec @IO $ runServerChecks y st case yPath of Left _ye -> MC.throw xe Right yMethod ->@@ -477,6 +490,7 @@ -- ** Type 'ServerErrorPath' newtype ServerErrorPath = ServerErrorPath Text deriving (Eq, Show)+ instance HTTP_Path Server where type PathConstraint Server a = Web.FromHttpApiData a segment expSegment = Server $ do@@ -567,12 +581,19 @@ Nothing -> Left $ Fail st [ServerErrorHeader] Just v -> Right $ Right ($ v) +instance HTTP_Raw Server where+ type RawConstraint Server = ()+ type RawArgs Server = Wai.Application+ type Raw Server = Wai.Application+ raw = Server $ return id+ -- ** Type 'ServerErrorBasicAuth' data ServerErrorBasicAuth = ServerErrorBasicAuth BasicAuthRealm (BasicAuth ()) deriving (Show) -- ** Class 'ServerBasicAuth'+-- | Custom 'BasicAuth' check. class ServerBasicAuth a where serverBasicAuth :: BasicAuthUser ->@@ -580,7 +601,7 @@ IO (BasicAuth a) -- | WARNING: current implementation of Basic Access Authentication--- is not immune to certian kinds of timing attacks.+-- is not immune to certain kinds of timing attacks. -- Decoding payloads does not take a fixed amount of time. instance HTTP_BasicAuth Server where type BasicAuthConstraint Server a = ServerBasicAuth a@@ -595,7 +616,7 @@ BasicAuth_BadPassword -> err BasicAuth_BadPassword BasicAuth_NoSuchUser -> err BasicAuth_NoSuchUser BasicAuth_Unauthorized -> err BasicAuth_Unauthorized- BasicAuth_Authorized a -> return ($ a)+ BasicAuth_Authorized u -> return ($ u) where -- | Find and decode an 'Authorization' header from the request as a Basic Auth decodeAuthorization :: Wai.Request -> Maybe (BasicAuthUser, BasicAuthPass)@@ -613,7 +634,7 @@ deriving (Eq, Show) -- *** Type 'ServerBodyArg'-newtype ServerBodyArg (ts::[*]) a = ServerBodyArg a+newtype ServerBodyArg (ts::[Type]) a = ServerBodyArg a instance HTTP_Body Server where type BodyConstraint Server a ts = MimeTypes ts (MimeDecodable a)@@ -637,13 +658,13 @@ Just (MimeType mt) -> do bodyBS <- MC.exec @IO $ Wai.requestBody $ serverState_request st return $ Right $ Right $ Right $- -- NOTE: delay 'mimeDecode' after all checks+ -- NOTE: delay 'mimeDecode' after all checks. case mimeDecode mt $ BSL.fromStrict bodyBS of Left err -> Left $ Fail st [ServerErrorBody err] Right a -> Right ($ ServerBodyArg a) -- *** Type 'ServerBodyStreamArg'-newtype ServerBodyStreamArg as (ts::[*]) framing+newtype ServerBodyStreamArg as (ts::[Type]) framing = ServerBodyStreamArg as instance HTTP_BodyStream Server where type BodyStreamConstraint Server as ts framing =@@ -678,13 +699,14 @@ ) -- * Type 'ServerResponse'--- | A continuation for |server|'s users to respond.+-- | A continuation for 'server''s users to respond. -- -- This newtype has two uses :--- * Carrying the 'ts' type variable to 'server'.--- * Providing a 'return' for the simple response case--- of 'HTTP.status200' and no extra headers.-newtype ServerRes (ts::[*]) m a+--+-- * Carrying the 'ts' type variable to 'server'.+-- * Providing a 'return' for the simple response case+-- of 'HTTP.status200' and no extra headers.+newtype ServerRes (ts::[Type]) m a = ServerResponse { unServerResponse :: m a } deriving (Functor, Applicative, Monad)@@ -752,11 +774,12 @@ -- * Type 'ServerResponseStream' -- -- This newtype has three uses :--- * Carrying the 'framing' type variable to 'server'.--- * Carrying the 'ts' type variable to 'server'.--- * Providing a 'return' for the simple response case--- of 'HTTP.status200' and no extra headers.-newtype ServerResStream framing (ts::[*]) m as+--+-- * Carrying the 'framing' type variable to 'server'.+-- * Carrying the 'ts' type variable to 'server'.+-- * Providing a 'return' for the simple response case+-- of 'HTTP.status200' and no extra headers.+newtype ServerResStream framing (ts::[Type]) m as = ServerResponseStream { unServerResponseStream :: m as } deriving (Functor, Applicative, Monad)@@ -778,9 +801,12 @@ type ResponseStreamArgs Server as ts framing = ServerResponseStream framing ts IO as type ResponseStream Server =+ Wai.Application+ {- Wai.Request -> (Wai.Response -> IO Wai.ResponseReceived) -> IO Wai.ResponseReceived+ -} responseStream :: forall as ts framing repr. ResponseStreamConstraint repr as ts framing =>@@ -855,3 +881,289 @@ instance Monoid HTTP.Status where mempty = HTTP.status200 mappend = (<>)++-- * Type 'Router'+-- | 'Trans'form a 'Server' to merge 'Alt'ernative 'segment's into a 'routing'.+data Router repr a b where+ -- | Lift any @(repr)@ into 'Router', those not useful to segregate+ -- wrt. the 'Trans'formation performed, aka. 'noTrans'.+ Router_Any :: repr a b -> Router repr a b+ -- | Represent 'segment'.+ Router_Seg :: PathSegment -> Router repr k k+ -- | Represent ('<.>').+ Router_Cat :: Router repr a b -> Router repr b c -> Router repr a c+ -- | Represent 'routing'.+ Router_Map :: Map.Map PathSegment (Router repr a k) -> Router repr a k+ -- | Represent ('<!>').+ Router_Alt :: Router repr a k -> Router repr b k -> Router repr (a:!:b) k+ -- | Represent 'capture''.+ Router_Cap :: PathConstraint Server a => Name -> Router repr (a->k) k+ -- | Represent 'captures'.+ Router_Caps :: Captures (Router repr) cs k -> Router repr (AltFromBinTree cs) k+ -- | Unify 'Router's which have different 'handlers'.+ -- Useful to put alternative 'Router's in a 'Map.Map' as in 'Router_Map'.+ Router_Union :: (b->a) -> Router repr a k -> Router repr b k++-- ** Type 'Captures'+data Captures repr (cs::BinTree Type) k where+ Captures0 :: PathConstraint Server a =>+ Proxy a -> Name -> repr x k ->+ Captures repr ('BinTree0 (a->x)) k+ Captures2 :: Captures repr x k ->+ Captures repr y k ->+ Captures repr ('BinTree2 x y) k++-- *** Type 'BinTree'+-- | Use @DataKinds@ to define a 'BinTree' of 'Type's.+-- Useful for gathering together 'capture's of different 'Type's.+data BinTree a+ = BinTree0 a+ | BinTree2 (BinTree a) (BinTree a)++-- *** Type family 'AltFromBinTree'+type family AltFromBinTree (cs::BinTree Type) :: Type where+ AltFromBinTree ('BinTree0 x) = x+ AltFromBinTree ('BinTree2 x y) = AltFromBinTree x :!: AltFromBinTree y++instance Trans (Router Server) where+ type UnTrans (Router Server) = Server+ noTrans = Router_Any+ unTrans (Router_Any x) = x+ unTrans (Router_Seg s) = segment s+ unTrans (Router_Cat x y) = unTrans x <.> unTrans y+ unTrans (Router_Alt x y) = unTrans x <!> unTrans y+ unTrans (Router_Map ms) = routing (unTrans <$> ms)+ unTrans (Router_Cap n) = capture' n+ unTrans (Router_Caps xs) = captures $ unTransCaptures xs+ where+ unTransCaptures :: Captures (Router Server) cs k -> Captures Server cs k+ unTransCaptures (Captures0 a n r) = Captures0 a n (unTrans r)+ unTransCaptures (Captures2 x y) = unTransCaptures x `Captures2` unTransCaptures y+ unTrans (Router_Union u x) = Server $ (. u) <$> unServer (unTrans x)++instance Cat (Router Server) where+ (<.>) = Router_Cat+instance Alt (Router Server) where+ (<!>) = Router_Alt+instance repr ~ Server => HTTP_Path (Router repr) where+ type PathConstraint (Router repr) a = PathConstraint repr a+ segment = Router_Seg+ capture' = Router_Cap+instance HTTP_Routing (Router Server) where+ routing = Router_Map+ captures = Router_Caps+instance HTTP_Raw (Router Server)+instance Pro (Router Server)+instance HTTP_Query (Router Server)+instance HTTP_Header (Router Server)+instance HTTP_Body (Router Server)+instance HTTP_BodyStream (Router Server)+instance HTTP_BasicAuth (Router Server)+instance HTTP_Response (Router Server)+instance HTTP_ResponseStream (Router Server)++-- ** Class 'HTTP_Routing'+class HTTP_Routing repr where+ routing :: Map.Map PathSegment (repr a k) -> repr a k+ captures :: Captures repr cs k -> repr (AltFromBinTree cs) k+ -- Trans defaults+ default routing ::+ Trans repr =>+ HTTP_Routing (UnTrans repr) =>+ Map.Map PathSegment (repr a k) -> repr a k+ routing = noTrans . routing . (unTrans <$>)+ default captures ::+ Trans repr =>+ HTTP_Routing (UnTrans repr) =>+ Captures repr cs k -> repr (AltFromBinTree cs) k+ captures = noTrans . captures . unTransCaptures+ where+ unTransCaptures :: Captures repr cs k -> Captures (UnTrans repr) cs k+ unTransCaptures (Captures0 a n r) = Captures0 a n (unTrans r)+ unTransCaptures (Captures2 x y) = Captures2 (unTransCaptures x) (unTransCaptures y)++instance HTTP_Routing Server where+ routing ms = Server $ do+ st@ServerState+ { serverState_request = req+ } <- S.get+ case Wai.pathInfo req of+ [] -> MC.throw $ Fail st [ServerErrorPath "empty path segment"]+ [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]+ curr:next ->+ case Map.lookup curr ms of+ Nothing -> MC.throw $ Fail st [ServerErrorPath $ "expected: "<>Text.pack (show (Map.keys ms))<>" but got: "<>curr]+ Just x -> do+ S.put st+ { serverState_request = req{ Wai.pathInfo = next }+ }+ unServer x+ + captures :: Captures Server cs k -> Server (AltFromBinTree cs) k+ captures cs = Server $ do+ st@ServerState+ { serverState_request = req+ } <- S.get+ case Wai.pathInfo req of+ [] -> MC.throw $ Fail st [ServerErrorPath "empty"]+ [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]+ currSeg:nextSeg ->+ case go cs of+ Left errs -> MC.throw $ Fail st+ [ServerErrorPath $ "captures: "<>+ fromString (List.intercalate "|" ((\(name,err) -> name) <$> errs))]+ Right a -> unServer a+ where+ go :: forall cs k. Captures Server cs k -> Either [(Name,Text)] (Server (AltFromBinTree cs) k)+ go (Captures0 (Proxy::Proxy a) name currRepr) =+ case Web.parseUrlPiece currSeg of+ Left err -> Left [(name,err)]+ Right (a::a) ->+ Right $ Server $ do+ S.put st { serverState_request = req{ Wai.pathInfo = nextSeg } }+ (\x2k a2x -> x2k (a2x a)) <$> unServer currRepr+ go (Captures2 x y) =+ case go x of+ Left xe ->+ case go y of+ Left ye -> Left (xe<>ye)+ Right a -> Right $ Server $ (\r2k (_l:!:r) -> r2k r) <$> unServer a+ Right a -> Right $ Server $ (\l2k (l:!:_r) -> l2k l) <$> unServer a++-- | Traverse a 'Router' to transform it:+--+-- * Associate 'Router_Cat' to the right.+-- * Replace 'Router_Seg' with 'Router_Map'.+-- * Replace 'Router_Cap' with 'Router_Caps'.+--+-- Used in 'server' on the 'Router' inferred from the given API.+router :: Router repr a b -> Router repr a b+router = {-debug1 "router" $-} \case+ x@Router_Any{} -> x+ x@Router_Seg{} -> x+ Router_Seg x `Router_Cat` y -> Router_Map $ Map.singleton x $ router y+ Router_Alt x y -> x`router_Alt`y+ Router_Map xs -> Router_Map $ router <$> xs+ Router_Cap xn `Router_Cat` x -> Router_Caps $ Captures0 Proxy xn x+ Router_Cap n -> Router_Cap n+ Router_Caps cs -> Router_Caps (go cs)+ where+ go :: Captures (Router repr) cs k -> Captures (Router repr) cs k+ go (Captures0 a n r) = Captures0 a n (router r)+ go (Captures2 x y) = Captures2 (go x) (go y)+ Router_Cat xy z ->+ case xy of+ Router_Cat x y ->+ -- Associate to the right+ Router_Cat (router x) $+ Router_Cat (router y) (router z)+ _ -> router xy `Router_Cat` router z+ Router_Union u x -> Router_Union u (router x)++-- | Merge/reorder alternatives if possible or default to a 'Router_Alt'.+router_Alt ::+ Router repr a k ->+ Router repr b k ->+ Router repr (a:!:b) k+router_Alt = {-debug2 "router_Alt"-} go+ where+ -- Merge alternative segments together.+ go (Router_Seg x `Router_Cat` xt) (Router_Seg y `Router_Cat` yt) =+ Map.singleton x (router xt)+ `router_Map`+ Map.singleton y (router yt)+ go (Router_Seg x `Router_Cat` xt) (Router_Map ys) =+ Map.singleton x (router xt)+ `router_Map` ys+ go (Router_Map xs) (Router_Seg y `Router_Cat` yt) =+ xs `router_Map`+ Map.singleton y (router yt)+ go (Router_Map xs) (Router_Map ys) =+ xs`router_Map`ys+ + -- Merge alternative 'segment's or alternative 'capture''s together.+ go (Router_Cap xn `Router_Cat` x) (Router_Cap yn `Router_Cat` y) =+ Router_Caps $+ Captures0 Proxy xn x+ `Captures2`+ Captures0 Proxy yn y+ go (Router_Caps xs) (Router_Caps ys) =+ Router_Caps $ xs`Captures2`ys+ go (Router_Cap xn `Router_Cat` x) (Router_Caps ys) =+ Router_Caps $ Captures0 Proxy xn x `Captures2` ys+ go (Router_Caps xs) (Router_Cap yn `Router_Cat` y) =+ Router_Caps $ xs `Captures2` Captures0 Proxy yn y+ + -- Merge left first or right first, depending on which removes 'Router_Alt'.+ go x (y`Router_Alt`z) =+ case x`router_Alt`y of+ Router_Alt x' y' ->+ case y'`router_Alt`z of+ yz@(Router_Alt _y z') ->+ case x'`router_Alt`z' of+ Router_Alt{} -> router x'`Router_Alt`yz+ xz -> Router_Union (\(a:!:(b:!:c)) -> (a:!:c):!:b) $ xz`router_Alt`y+ -- NOTE: prioritize the merged router 'xz' over over the non-mergeable 'y'.+ yz -> x'`router_Alt`yz+ xy -> Router_Union (\(a:!:(b:!:c)) -> (a:!:b):!:c) $ xy`router_Alt`z+ go (x`Router_Alt`y) z =+ case y`router_Alt`z of+ Router_Alt y' z' ->+ case x`router_Alt`y' of+ xy@(Router_Alt x' _y) ->+ case x'`router_Alt`z' of+ Router_Alt{} -> xy`Router_Alt`router z'+ xz -> Router_Union (\((a:!:b):!:c) -> (a:!:c):!:b) $ xz`router_Alt`y+ -- NOTE: prioritize the merged router 'xz' over the non-mergeable 'y'.+ xy -> xy`router_Alt`z'+ yz -> Router_Union (\((a:!:b):!:c) -> a:!:(b:!:c)) $ x`router_Alt`yz+ + -- Merge through 'Router_Union'.+ go (Router_Union u x) y = Router_Union (\(a:!:b) -> u a:!:b) (x`router_Alt`y)+ go x (Router_Union u y) = Router_Union (\(a:!:b) -> a:!:u b) (x`router_Alt`y)+ + -- No merging, but apply 'router' on both alternatives.+ go x y = router x `Router_Alt` router y++router_Map ::+ Map.Map PathSegment (Router repr a k) ->+ Map.Map PathSegment (Router repr b k) ->+ Router repr (a:!:b) k+router_Map xs ys =+ -- NOTE: a little bit more complex than required+ -- in order to merge 'Router_Union's instead of stacking them,+ -- such that 'unTrans' 'Router_Union' applies them all at once.+ Router_Map $+ Map.merge+ (Map.traverseMissing $ const $ \case+ Router_Union u r ->+ return $ Router_Union (\(x:!:_y) -> u x) r+ r -> return $ Router_Union (\(x:!:_y) -> x) r)+ (Map.traverseMissing $ const $ \case+ Router_Union u r ->+ return $ Router_Union (\(_x:!:y) -> u y) r+ r -> return $ Router_Union (\(_x:!:y) -> y) r)+ (Map.zipWithAMatched $ const $ \case+ Router_Union xu xr -> \case+ Router_Union yu yr ->+ return $ Router_Union (\(x:!:y) -> xu x:!:yu y) $ xr`router_Alt`yr+ yr ->+ return $ Router_Union (\(a:!:b) -> xu a:!:b) $ xr`router_Alt`yr+ xr -> \case+ Router_Union yu yr ->+ return $ Router_Union (\(a:!:b) -> a:!:yu b) $ xr`router_Alt`yr+ yr -> return $ xr`router_Alt`yr)+ xs ys++{-+debug0 :: Show a => String -> a -> a+debug0 n a = Debug.trace (" {"<>n<>": "<>show a) a+debug1 :: Show a => Show b => String -> (a->b) -> (a->b)+debug1 n a2b a = Debug.trace ("} "<>n<>": r: "<>show b) b+ where b = a2b $ Debug.trace ("{ "<>n<>": a: "<>show a) a+debug2 :: Show a => Show b => Show c => String -> (a->b->c) -> (a->b->c)+debug2 n a2b2c a b = Debug.trace ("} "<>n<>": r: "<>show c) c+ where+ b2c = a2b2c $ Debug.trace ("{ "<>n<>": a: "<>show a) a+ c = b2c $ Debug.trace (n<>": b: "<>show b) b+-}
symantic-http-server.cabal view
@@ -2,12 +2,12 @@ -- PVP: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.0.0.20190324+version: 0.1.1.20190410 category: Protocol synopsis: symantic-http applied to the derivation of HTTP servers description: This library applies <https://hackage.haskell.org/package/symantic-http symantic-http>- to the building of an HTTP server (request routing and decoding)+ to the building of an HTTP server (request routing and response building) based upon <https://hackage.haskell.org/package/warp warp>, . For learning how to use this library,@@ -60,9 +60,9 @@ -fno-warn-tabs -- -fhide-source-paths build-depends:- symantic-http >= 0.0+ symantic-http >= 0.1.1 , base >= 4.10 && < 5- , base64-bytestring >= 1.0.0.1+ , base64-bytestring >= 1.0 , bytestring >= 0.10 , containers >= 0.5 , http-api-data >= 0.4