-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Lightweight library for building HTTP API -- -- Please see the documentation at http://linnet.io @package linnet @version 0.1.0.2 module Linnet.ContentTypes -- | Content-Type literal for text/html encoding type TextHtml = "text/html" -- | Content-Type literal for text/plain encoding type TextPlain = "text/plain" -- | Content-Type literal for application/json encoding type ApplicationJson = "application/json" module Linnet.Encode -- | Encoding of some type a into payload of HTTP response Phantom -- type ct guarantees that compiler checks support of encoding -- of some a into content of given Content-Type by -- looking for specific Encode instance. class Encode (ct :: Symbol) a encode :: Encode ct a => a -> ByteString instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain Data.ByteString.Lazy.Internal.ByteString instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain Data.ByteString.Internal.ByteString instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain Data.Text.Internal.Text instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain Data.Text.Internal.Lazy.Text instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain GHC.Types.Int instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain GHC.Integer.Type.Integer instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain GHC.Types.Double instance Linnet.Encode.Encode Linnet.ContentTypes.TextPlain GHC.Types.Float module Linnet.Endpoints.Entity data Entity Param :: ByteString -> Entity [entityName] :: Entity -> ByteString Header :: ByteString -> Entity [entityName] :: Entity -> ByteString Body :: Entity Cookie :: ByteString -> Entity [entityName] :: Entity -> ByteString instance GHC.Show.Show Linnet.Endpoints.Entity.Entity instance GHC.Classes.Eq Linnet.Endpoints.Entity.Entity module Linnet.Errors data LinnetError MissingEntity :: Entity -> LinnetError [missingEntity] :: LinnetError -> Entity EntityNotParsed :: Entity -> LinnetError -> LinnetError [notParsedEntity] :: LinnetError -> Entity [entityParsingError] :: LinnetError -> LinnetError LinnetErrors :: NonEmpty LinnetError -> LinnetError [linnetErrors] :: LinnetError -> NonEmpty LinnetError DecodeError :: ByteString -> LinnetError [decodeError] :: LinnetError -> ByteString instance GHC.Show.Show Linnet.Errors.LinnetError instance GHC.Classes.Eq Linnet.Errors.LinnetError instance GHC.Base.Semigroup Linnet.Errors.LinnetError instance GHC.Exception.Type.Exception Linnet.Errors.LinnetError module Linnet.Decode -- | Decoding of HTTP request payload into some type a. Phantom -- type ct guarantees that compiler checks support of decoding -- some a from content of given Content-Type by looking -- for specific Decode instance. class Decode (ct :: Symbol) a decode :: Decode ct a => ByteString -> Either LinnetError a class DecodePath a decodePath :: DecodePath a => Text -> Maybe a class DecodeEntity a decodeEntity :: DecodeEntity a => Entity -> ByteString -> Either LinnetError a decodeEntity :: (DecodeEntity a, FromByteString a) => Entity -> ByteString -> Either LinnetError a instance Linnet.Decode.DecodeEntity Data.ByteString.Internal.ByteString instance Linnet.Decode.DecodeEntity Data.Text.Internal.Text instance Linnet.Decode.DecodeEntity GHC.Integer.Type.Integer instance Linnet.Decode.DecodeEntity GHC.Types.Int instance Linnet.Decode.DecodeEntity GHC.Types.Double instance Linnet.Decode.DecodeEntity GHC.Types.Float instance Linnet.Decode.DecodePath Data.Text.Internal.Text instance Linnet.Decode.DecodePath Data.ByteString.Internal.ByteString instance Linnet.Decode.DecodePath GHC.Integer.Type.Integer instance Linnet.Decode.DecodePath GHC.Types.Int instance Linnet.Decode.DecodePath GHC.Types.Double instance Linnet.Decode.DecodePath GHC.Types.Float module Linnet.Input -- | Container for the reminder of the request path and request itself data Input Input :: [Text] -> Request -> Input [reminder] :: Input -> [Text] [request] :: Input -> Request inputGet :: Text -> [QueryItem] -> Input inputFromRequest :: Request -> Input instance GHC.Show.Show Linnet.Input.Input module Linnet.Internal.Coproduct data Coproduct a b [Inl] :: a -> Coproduct a b [Inr] :: b -> Coproduct a b data CNil -- | Flatten nested coproduct class AdjoinCoproduct cs c | cs -> c adjoinCoproduct :: AdjoinCoproduct cs c => cs -> c instance (GHC.Classes.Eq a, GHC.Classes.Eq b) => GHC.Classes.Eq (Linnet.Internal.Coproduct.Coproduct a b) instance Linnet.Internal.Coproduct.AdjoinCoproduct' (Linnet.Internal.Coproduct.AdjoinCoproductT cs) cs c => Linnet.Internal.Coproduct.AdjoinCoproduct cs c instance Linnet.Internal.Coproduct.AdjoinCoproduct' 'GHC.Types.False Linnet.Internal.Coproduct.CNil Linnet.Internal.Coproduct.CNil instance (h Data.Type.Equality.~ h', Linnet.Internal.Coproduct.AdjoinCoproduct t out) => Linnet.Internal.Coproduct.AdjoinCoproduct' 'GHC.Types.False (Linnet.Internal.Coproduct.Coproduct h' t) (Linnet.Internal.Coproduct.Coproduct h out) instance (Linnet.Internal.Coproduct.AdjoinCoproduct t out, Linnet.Internal.Coproduct.ExtendBy h out out') => Linnet.Internal.Coproduct.AdjoinCoproduct' 'GHC.Types.True (Linnet.Internal.Coproduct.Coproduct h t) out' instance Linnet.Internal.Coproduct.Reverse' Linnet.Internal.Coproduct.CNil cs out => Linnet.Internal.Coproduct.Reverse cs out instance Linnet.Internal.Coproduct.Reverse' acc Linnet.Internal.Coproduct.CNil acc instance Linnet.Internal.Coproduct.Reverse' (Linnet.Internal.Coproduct.Coproduct a acc) b out => Linnet.Internal.Coproduct.Reverse' acc (Linnet.Internal.Coproduct.Coproduct a b) out instance (Linnet.Internal.Coproduct.Reverse l revL, Linnet.Internal.Coproduct.ExtendLeftBy' revL r out) => Linnet.Internal.Coproduct.ExtendLeftBy l r out instance Linnet.Internal.Coproduct.ExtendRight' (Linnet.Internal.Coproduct.ExtendRightT cs) cs t out => Linnet.Internal.Coproduct.ExtendRight cs t out instance Linnet.Internal.Coproduct.ExtendRight' 'GHC.Types.True (Linnet.Internal.Coproduct.Coproduct h Linnet.Internal.Coproduct.CNil) a (Linnet.Internal.Coproduct.Coproduct h (Linnet.Internal.Coproduct.Coproduct a Linnet.Internal.Coproduct.CNil)) instance Linnet.Internal.Coproduct.ExtendRight t a out => Linnet.Internal.Coproduct.ExtendRight' 'GHC.Types.False (Linnet.Internal.Coproduct.Coproduct h t) a (Linnet.Internal.Coproduct.Coproduct h out) instance (Linnet.Internal.Coproduct.ExtendRight l h out, Linnet.Internal.Coproduct.ExtendRightBy out t out') => Linnet.Internal.Coproduct.ExtendRightBy l (Linnet.Internal.Coproduct.Coproduct h t) out' instance (Linnet.Internal.Coproduct.ExtendLeftBy l r out, Linnet.Internal.Coproduct.ExtendRightBy l r out) => Linnet.Internal.Coproduct.ExtendBy l r out instance Linnet.Internal.Coproduct.ExtendRightBy l Linnet.Internal.Coproduct.CNil l instance Linnet.Internal.Coproduct.ExtendLeftBy' Linnet.Internal.Coproduct.CNil a a instance Linnet.Internal.Coproduct.ExtendLeftBy' t (Linnet.Internal.Coproduct.Coproduct h r) out => Linnet.Internal.Coproduct.ExtendLeftBy' (Linnet.Internal.Coproduct.Coproduct h t) r out instance GHC.Classes.Eq Linnet.Internal.Coproduct.CNil module Linnet.Internal.HList data HList xs [HNil] :: HList '[] [:::] :: a -> HList as -> HList (a : as) infixr 6 ::: class AdjoinHList ls l | ls -> l adjoin :: AdjoinHList ls l => HList ls -> HList l class FnToProduct fn ls out | fn ls -> out, ls out -> fn fromFunction :: FnToProduct fn ls out => fn -> HList ls -> out instance (v Data.Type.Equality.~ fn) => Linnet.Internal.HList.FnToProduct fn '[] v instance Linnet.Internal.HList.FnToProduct fnOut tail out => Linnet.Internal.HList.FnToProduct (input -> fnOut) (input : tail) out instance Linnet.Internal.HList.AdjoinHList' (Linnet.Internal.HList.NeedAdjoin ls) ls l => Linnet.Internal.HList.AdjoinHList ls l instance Linnet.Internal.HList.AdjoinHList' 'GHC.Types.False '[] '[] instance Linnet.Internal.HList.AdjoinHList t out => Linnet.Internal.HList.AdjoinHList' 'GHC.Types.False (h : t) (h : out) instance (Linnet.Internal.HList.AdjoinHList t ao, Linnet.Internal.HList.Prepend a ao po) => Linnet.Internal.HList.AdjoinHList' 'GHC.Types.True (Linnet.Internal.HList.HList a : t) po instance Linnet.Internal.HList.Prepend' (Linnet.Internal.HList.PrependT a b) a b ab => Linnet.Internal.HList.Prepend a b ab instance Linnet.Internal.HList.Prepend' 'Linnet.Internal.HList.BothHNil '[] '[] '[] instance Linnet.Internal.HList.Prepend' 'Linnet.Internal.HList.LeftHNil '[] b b instance Linnet.Internal.HList.Prepend' 'Linnet.Internal.HList.RightHNil a '[] a instance Linnet.Internal.HList.Prepend as (b : bs) cs => Linnet.Internal.HList.Prepend' 'Linnet.Internal.HList.NoneHNil (a : as) (b : bs) (a : cs) instance GHC.Show.Show (Linnet.Internal.HList.HList '[]) instance (GHC.Show.Show a, GHC.Show.Show (Linnet.Internal.HList.HList as)) => GHC.Show.Show (Linnet.Internal.HList.HList (a : as)) instance GHC.Classes.Eq (Linnet.Internal.HList.HList '[]) instance (GHC.Classes.Eq a, GHC.Classes.Eq (Linnet.Internal.HList.HList as)) => GHC.Classes.Eq (Linnet.Internal.HList.HList (a : as)) module Linnet.ToResponse -- | Type-class to convert a value of type a into Response with -- Content-Type of ct class ToResponse (ct :: Symbol) a toResponse :: ToResponse ct a => a -> Response instance Linnet.ToResponse.ToResponse' (Linnet.ToResponse.ValueT a) ct a => Linnet.ToResponse.ToResponse ct a instance (Linnet.Encode.Encode ct a, GHC.TypeLits.KnownSymbol ct) => Linnet.ToResponse.ToResponse' 'Linnet.ToResponse.Value ct a instance Linnet.ToResponse.ToResponse' 'Linnet.ToResponse.ResponseValue ct Network.Wai.Internal.Response instance GHC.TypeLits.KnownSymbol ct => Linnet.ToResponse.ToResponse' 'Linnet.ToResponse.UnitValue ct () instance Linnet.ToResponse.ToResponse' 'Linnet.ToResponse.CNilValue ct Linnet.Internal.Coproduct.CNil instance (Linnet.ToResponse.ToResponse ct a, Linnet.ToResponse.ToResponse ct b) => Linnet.ToResponse.ToResponse' 'Linnet.ToResponse.CoproductValue ct (Linnet.Internal.Coproduct.Coproduct a b) module Linnet.Output -- | Output of Endpoint that carries some Payload -- a together with response status and headers data Output a Output :: Status -> Payload a -> [Header] -> Output a [outputStatus] :: Output a -> Status [outputPayload] :: Output a -> Payload a [outputHeaders] :: Output a -> [Header] -- | Payload of Output that could be: data Payload a -- | Payload with some value a Payload :: a -> Payload a -- | Represents empty response NoPayload :: Payload a -- | Failed payload with an exception inside ErrorPayload :: e -> Payload a -- | Create Output with Payload a and status OK -- 200 ok :: a -> Output a -- | Create Output with Payload a and status -- CREATED 201 created :: a -> Output a -- | Create Output with NoPayload and status ACCEPTED -- 202 accepted :: Output a -- | Create Output with NoPayload and status NO CONTENT -- 202 noContent :: Output a -- | Create Output with ErrorPayload e and status BAD -- REQUEST 400 badRequest :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- UNAUTHORIZED 401 unauthorized :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- PAYMENT REQUIRED 402 paymentRequired :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- FORBIDDEN 403 forbidden :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status NOT -- FOUND 404 notFound :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- METHOD NOT ALLOWED 405 methodNotAllowed :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status NOT -- ACCEPTABLE 406 notAcceptable :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- CONFLICT 409 conflict :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status GONE -- 410 gone :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- LENGTH REQUIRED 411 lengthRequired :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- PRECONDITIONED FAILED 412 preconditionFailed :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- REQUEST ENTITY TOO LARGE 413 requestEntityTooLarge :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- UNPROCESSABLE ENTITY 422 unprocessableEntity :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status TOO -- MANY REQUESTS 422 tooManyRequests :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- INTERNAL SERVER ERROR 500 internalServerError :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status NOT -- IMPLEMENTED 501 notImplemented :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status BAD -- GATEWAY 502 badGateway :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- SERVICE UNAVAILABLE 503 serviceUnavailable :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- GATEWAY TIMEOUT 504 gatewayTimeout :: Exception e => e -> Output a -- | Create successful Output with payload a and given -- status payloadOutput :: Status -> a -> Output a -- | Create failed Output with exception e and given status payloadError :: Exception e => Status -> e -> Output a -- | Create empty Output with given status payloadEmpty :: Status -> Output a -- | Transform payload of output transformM :: Applicative m => (a -> m (Output b)) -> Output a -> m (Output b) -- | Add header to given Output withHeader :: (ByteString, ByteString) -> Output a -> Output a outputToResponse :: forall a ct. (KnownSymbol ct, ToResponse ct a, ToResponse ct SomeException) => Output a -> Response instance GHC.Classes.Eq a => GHC.Classes.Eq (Linnet.Output.Output a) instance GHC.Show.Show a => GHC.Show.Show (Linnet.Output.Payload a) instance GHC.Show.Show a => GHC.Show.Show (Linnet.Output.Output a) instance GHC.Base.Functor Linnet.Output.Output instance GHC.Base.Applicative Linnet.Output.Output instance GHC.Base.Monad Linnet.Output.Output instance Control.Monad.Catch.MonadThrow Linnet.Output.Output instance Data.Foldable.Foldable Linnet.Output.Output instance Data.Traversable.Traversable Linnet.Output.Output instance GHC.Classes.Eq a => GHC.Classes.Eq (Linnet.Output.Payload a) module Linnet.Endpoint -- | Result of returned by Endpoint that could be either: -- -- data EndpointResult (m :: * -> *) a Matched :: Input -> m (Output a) -> EndpointResult a [matchedReminder] :: EndpointResult a -> Input [matchedOutput] :: EndpointResult a -> m (Output a) NotMatched :: EndpointResult a -- | Basic Linnet data type that abstracts away operations over HTTP -- communication. While WAI Application has type of Request -> -- (Response -> IO ResponseReceived) -> IO ResponseReceived, -- it's practical to treat web applications as functions of Request -- -> BusinessLogic -> IO Response where -- BusinessLogic is usually a function of a -> m b -- where a and b are data to be decoded from the -- request / encoded to response, m is some monad, and this is -- the most interesting part of an application. -- -- Endpoint's purpose is exactly to abstract details of encoding and -- decoding, along with routing and the rest, and provide simple -- interface to encapsulate BusinessLogic into a final web -- application. -- -- Business logic is encoded as transformation in fmap, -- mapOutput, mapOutputM, mapM and the like. -- Usual way to transform endpoint is to use ~> and -- ~>> operators: -- --
--   get (path @Text) ~> (\segment -> return $ ok segment)
--   
-- -- Here, ~> is just an inverted alias for mapOutputM -- function. Often, endpoint is a product of multiple endpoints, and here -- ~>> proves to be very handy: -- --
--   get (p' "sum" // path @Int // path @Int) ~>> (\i1 i2 -> return $ ok (i1 + i2) )
--   
-- -- The trick is that // defines sequential AND -- combination of endpoints that is represented as endpoint of -- HList, so instead of dealing with heterogeneous list, it's -- possible to use ~>> instead and map with a function of -- multiple arguments. -- -- Endpoints are also composable in terms of OR logic with -- |+| operator that is useful for routing: -- --
--   getUsers = get (p' "users") ~>> (ok <$> fetchUsers)
--   newUser = post (p' "users" // jsonBody @User) ~>> (\user -> ok <$> createUser user)
--   usersApi = getUsers |+| newUser
--   
-- -- An endpoint might be converted into WAI Application using -- bootstrap and @TypeApplications language pragma: -- --
--   main = run 9000 app
--          where app = bootstrap @TextPlain usersApi & compile & toApp id
--   
data Endpoint (m :: * -> *) a Endpoint :: (Input -> EndpointResult m a) -> String -> Endpoint a [runEndpoint] :: Endpoint a -> Input -> EndpointResult m a [toString] :: Endpoint a -> String isMatched :: EndpointResult m a -> Bool maybeReminder :: EndpointResult m a -> Maybe Input -- | Lift monadic value m a into Endpoint that always -- matches lift :: Functor m => m a -> Endpoint m a -- | Lift monadic output m (Output a) into Endpoint that -- always matches liftOutputM :: m (Output a) -> Endpoint m a -- | Map over the Output of endpoint with function returning new -- value a lifted in monad m mapM' :: Monad m => (a -> m b) -> Endpoint m a -> Endpoint m b -- | Map over the value of Endpoint with function returning new -- Output b mapOutput :: Monad m => (a -> Output b) -> Endpoint m a -> Endpoint m b -- | Map over the value of Endpoint with function returning new -- m (Output b) mapOutputM :: Monad m => (a -> m (Output b)) -> Endpoint m a -> Endpoint m b -- | Handle exception in monad m of Endpoint result using provided -- function that returns new Output handle :: (MonadCatch m, Exception e) => (e -> m (Output a)) -> Endpoint m a -> Endpoint m a -- | Handle all exceptions in monad m of Endpoint result handleAll :: MonadCatch m => (SomeException -> m (Output a)) -> Endpoint m a -> Endpoint m a -- | Lift an exception of type e into Either try :: (Exception e, MonadCatch m) => Endpoint m a -> Endpoint m (Either e a) transformOutput :: (m (Output a) -> m (Output b)) -> Endpoint m a -> Endpoint m b transform :: Monad m => (m a -> m b) -> Endpoint m a -> Endpoint m b -- | Inversed alias for mapOutputM (~>) :: Monad m => Endpoint m a -> (a -> m (Output b)) -> Endpoint m b infixl 0 ~> -- | Advanced version of ~> operator that allows to map -- Endpoint m (HList ls) over a function of arity N equal to N -- elements of HList. General rule of thumb when to use this operator is -- whenever there is an HList on the left side. (~>>) :: (Monad m, FnToProduct fn ls (m (Output b))) => Endpoint m (HList ls) -> fn -> Endpoint m b infixl 0 ~>> -- | Create product of two Endpoints that sequentially match a -- request. | If some of endpoints doesn't match a request, the final -- result is also non-matching productWith :: forall m a b c. MonadCatch m => Endpoint m a -> Endpoint m b -> (a -> b -> c) -> Endpoint m c -- | Create product of two Endpoints that sequentially match a -- request and values are adjoined into HList. If some of -- endpoints doesn't match a request, the final result is also -- non-matching (//) :: (MonadCatch m, AdjoinHList (a : (b : '[])) out) => Endpoint m a -> Endpoint m b -> Endpoint m (HList out) infixr 2 // -- | Create new Endpoint of two endpoints, adjoining values into -- Coproduct During request resolution the following logic is -- applied: -- -- (|+|) :: forall m a b out. (MonadCatch m, AdjoinCoproduct (Coproduct a (Coproduct b CNil)) out) => Endpoint m a -> Endpoint m b -> Endpoint m out infixl 2 |+| -- | Endpoint that always matches and returns a request from Input root :: Applicative m => Endpoint m Request -- | Endpoint that always matches and doesn't change any reminder zero :: Applicative m => Endpoint m (HList '[]) instance GHC.Show.Show (Linnet.Endpoint.Endpoint m a) instance GHC.Base.Functor m => GHC.Base.Functor (Linnet.Endpoint.Endpoint m) instance Control.Monad.Catch.MonadCatch m => GHC.Base.Applicative (Linnet.Endpoint.Endpoint m) instance Control.Monad.Catch.MonadCatch m => GHC.Base.Alternative (Linnet.Endpoint.Endpoint m) instance GHC.Show.Show (m (Linnet.Output.Output a)) => GHC.Show.Show (Linnet.Endpoint.EndpointResult m a) instance GHC.Base.Functor m => GHC.Base.Functor (Linnet.Endpoint.EndpointResult m) module Linnet.Endpoints.Paths -- | Endpoint that tries to decode head of the current path reminder into -- specific type. It consumes head of the reminder. -- -- path :: forall a m. (DecodePath a, Applicative m, Typeable a) => Endpoint m a -- | Endpoint that matches only if the head of current path reminder is -- equal to some given constant value. It consumes head of the reminder. -- -- pathConst :: Applicative m => Text -> Endpoint m (HList '[]) -- | Short alias for pathConst p' :: Applicative m => Text -> Endpoint m (HList '[]) -- | Endpoint that matches only against empty path reminder pathEmpty :: Applicative m => Endpoint m (HList '[]) -- | Endpoint that consumes the rest of the path reminder and decode it -- using provided DecodePath for some type a paths :: forall a m. (DecodePath a, Applicative m, Typeable a) => Endpoint m [a] -- | Endpoint that matches any path and discards reminder pathAny :: Applicative m => Endpoint m (HList '[]) module Linnet.Endpoints.Params -- | Endpoint that tries to decode parameter name from the request -- query string. Always matches, but may throw an exception in case: -- -- param :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode parameter name from the request -- query string. Always matches, but may throw an exception in case: -- -- paramMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) -- | Endpoint that tries to decode all parameters name from the -- request query string. Always matches, but may throw an exception in -- case: -- -- params :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m [a] -- | Endpoint that tries to decode all parameters name from the -- request query string. Always matches, but may throw an exception in -- case: -- -- paramsNel :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (NonEmpty a) module Linnet.Endpoints.Methods -- | Turn endpoint into one that matches only for GET requests get :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for POST requests post :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for PUT requests put :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for PATCH requests patch :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for DELETE requests delete :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for HEAD requests head' :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for TRACE requests trace' :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for CONNECT requests connect :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for OPTIONS requests options :: Endpoint m a -> Endpoint m a module Linnet.Endpoints.Headers -- | Endpoint that tries to decode header name from a request. -- Always matches, but may throw an exception in case: -- -- header :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode header name from a request. -- Always matches, but may throw an exception in case: -- -- headerMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) module Linnet.Endpoints.Cookies -- | Endpoint that tries to decode cookie name from a request. -- Always matches, but may throw an exception in case: -- -- cookie :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode cookie name from a request. -- Always matches, but may throw an exception in case: -- -- cookieMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) module Linnet.Endpoints.Bodies -- | Endpoint that tries to decode body of request into some type -- a using corresponding Decode instance. Matches if body -- isn't chunked. May throw an exception in case: -- -- body :: forall ct a m. (Decode ct a, MonadIO m, MonadThrow m) => Endpoint m a -- | Endpoint that tries to decode body of request into some type -- a using corresponding Decode instance. Matches if body -- isn't chunked. May throw an exception in case: -- -- bodyMaybe :: forall ct a m. (Decode ct a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) -- | Alias for body @ApplicationJson jsonBody :: (Decode ApplicationJson a, MonadIO m, MonadThrow m) => Endpoint m a -- | Alias for bodyMaybe @ApplicationJson jsonBodyMaybe :: (Decode ApplicationJson a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) -- | Alias for body @TextPlain textBody :: (Decode TextPlain a, MonadIO m, MonadThrow m) => Endpoint m a -- | Alias for bodyMaybe @TextPlain textBodyMaybe :: (Decode TextPlain a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) module Linnet.Endpoints -- | Endpoint that tries to decode cookie name from a request. -- Always matches, but may throw an exception in case: -- -- cookie :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode cookie name from a request. -- Always matches, but may throw an exception in case: -- -- cookieMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) -- | Endpoint that tries to decode header name from a request. -- Always matches, but may throw an exception in case: -- -- header :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode header name from a request. -- Always matches, but may throw an exception in case: -- -- headerMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) -- | Turn endpoint into one that matches only for GET requests get :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for POST requests post :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for PUT requests put :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for PATCH requests patch :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for DELETE requests delete :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for HEAD requests head' :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for TRACE requests trace' :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for CONNECT requests connect :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for OPTIONS requests options :: Endpoint m a -> Endpoint m a -- | Endpoint that tries to decode parameter name from the request -- query string. Always matches, but may throw an exception in case: -- -- param :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode parameter name from the request -- query string. Always matches, but may throw an exception in case: -- -- paramMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) -- | Endpoint that tries to decode all parameters name from the -- request query string. Always matches, but may throw an exception in -- case: -- -- params :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m [a] -- | Endpoint that tries to decode all parameters name from the -- request query string. Always matches, but may throw an exception in -- case: -- -- paramsNel :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (NonEmpty a) -- | Endpoint that tries to decode head of the current path reminder into -- specific type. It consumes head of the reminder. -- -- path :: forall a m. (DecodePath a, Applicative m, Typeable a) => Endpoint m a -- | Endpoint that matches only if the head of current path reminder is -- equal to some given constant value. It consumes head of the reminder. -- -- pathConst :: Applicative m => Text -> Endpoint m (HList '[]) -- | Short alias for pathConst p' :: Applicative m => Text -> Endpoint m (HList '[]) -- | Endpoint that matches only against empty path reminder pathEmpty :: Applicative m => Endpoint m (HList '[]) -- | Endpoint that consumes the rest of the path reminder and decode it -- using provided DecodePath for some type a paths :: forall a m. (DecodePath a, Applicative m, Typeable a) => Endpoint m [a] -- | Endpoint that matches any path and discards reminder pathAny :: Applicative m => Endpoint m (HList '[]) -- | Endpoint that tries to decode body of request into some type -- a using corresponding Decode instance. Matches if body -- isn't chunked. May throw an exception in case: -- -- body :: forall ct a m. (Decode ct a, MonadIO m, MonadThrow m) => Endpoint m a -- | Endpoint that tries to decode body of request into some type -- a using corresponding Decode instance. Matches if body -- isn't chunked. May throw an exception in case: -- -- bodyMaybe :: forall ct a m. (Decode ct a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) -- | Alias for body @TextPlain textBody :: (Decode TextPlain a, MonadIO m, MonadThrow m) => Endpoint m a -- | Alias for bodyMaybe @TextPlain textBodyMaybe :: (Decode TextPlain a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) -- | Alias for body @ApplicationJson jsonBody :: (Decode ApplicationJson a, MonadIO m, MonadThrow m) => Endpoint m a -- | Alias for bodyMaybe @ApplicationJson jsonBodyMaybe :: (Decode ApplicationJson a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) module Linnet.Compile class Compile cts m es compile :: Compile cts m es => es -> ReaderT Request m Response instance GHC.Base.Monad m => Linnet.Compile.Compile Linnet.Internal.Coproduct.CNil m (Linnet.Internal.HList.HList '[]) instance (GHC.TypeLits.KnownSymbol ct, Linnet.ToResponse.ToResponse ct a, Linnet.ToResponse.ToResponse ct GHC.Exception.Type.SomeException, Linnet.Compile.Compile cts m (Linnet.Internal.HList.HList es), Control.Monad.Catch.MonadCatch m) => Linnet.Compile.Compile (Linnet.Internal.Coproduct.Coproduct (Data.Proxy.Proxy ct) cts) m (Linnet.Internal.HList.HList (Linnet.Endpoint.Endpoint m a : es)) module Linnet.Bootstrap -- | Create Bootstrap out of single Endpoint and some given -- Content-Type: -- --
--   bootstrap @TextPlain (pure "foo")
--   
bootstrap :: forall (ct :: Symbol) m a. Endpoint m a -> Bootstrap m (Coproduct (Proxy ct) CNil) (HList '[Endpoint m a]) -- | Add another endpoint to Bootstrap for purpose of serving -- multiple Content-Types with *different* endpoints -- --
--   bootstrap @TextPlain (pure "foo") & server @ApplicationJson (pure "bar")
--   
serve :: forall (ct :: Symbol) cts es m a. Endpoint m a -> Bootstrap m cts (HList es) -> Bootstrap m (Coproduct (Proxy ct) cts) (HList (Endpoint m a : es)) -- | Compile Bootstrap into ReaderT Request m Response for -- further combinations. Might be useful to implement middleware in -- context of the same monad m: -- --
--   bootstrap @TextPlain (pure "foo") & compile
--   
compile :: forall cts m es. Compile cts m es => Bootstrap m cts es -> ReaderT Request m Response -- | Convert ReaderT Request m Response into WAI -- Application -- --
--   bootstrap @TextPlain (pure "foo") & compile & toApp id
--   
-- -- The first parameter here is a natural transformation of -- Endpoints monad m into IO. In case if -- selected monad is IO already then id is just enough. -- Otherwise, it's a good place to define how to "start" custom monad for -- each request to come and convert it to IO. -- -- As an example: -- -- toApp :: (forall a. m a -> IO a) -> ReaderT Request m Response -> Application -- | Linnet [ˈlɪnɪt] is a lightweight Haskell library for building HTTP API -- on top of WAI. Library design is heavily inspired by Scala -- Finch. -- -- See the detailed documentation on linnet.io. module Linnet -- | Basic Linnet data type that abstracts away operations over HTTP -- communication. While WAI Application has type of Request -> -- (Response -> IO ResponseReceived) -> IO ResponseReceived, -- it's practical to treat web applications as functions of Request -- -> BusinessLogic -> IO Response where -- BusinessLogic is usually a function of a -> m b -- where a and b are data to be decoded from the -- request / encoded to response, m is some monad, and this is -- the most interesting part of an application. -- -- Endpoint's purpose is exactly to abstract details of encoding and -- decoding, along with routing and the rest, and provide simple -- interface to encapsulate BusinessLogic into a final web -- application. -- -- Business logic is encoded as transformation in fmap, -- mapOutput, mapOutputM, mapM and the like. -- Usual way to transform endpoint is to use ~> and -- ~>> operators: -- --
--   get (path @Text) ~> (\segment -> return $ ok segment)
--   
-- -- Here, ~> is just an inverted alias for mapOutputM -- function. Often, endpoint is a product of multiple endpoints, and here -- ~>> proves to be very handy: -- --
--   get (p' "sum" // path @Int // path @Int) ~>> (\i1 i2 -> return $ ok (i1 + i2) )
--   
-- -- The trick is that // defines sequential AND -- combination of endpoints that is represented as endpoint of -- HList, so instead of dealing with heterogeneous list, it's -- possible to use ~>> instead and map with a function of -- multiple arguments. -- -- Endpoints are also composable in terms of OR logic with -- |+| operator that is useful for routing: -- --
--   getUsers = get (p' "users") ~>> (ok <$> fetchUsers)
--   newUser = post (p' "users" // jsonBody @User) ~>> (\user -> ok <$> createUser user)
--   usersApi = getUsers |+| newUser
--   
-- -- An endpoint might be converted into WAI Application using -- bootstrap and @TypeApplications language pragma: -- --
--   main = run 9000 app
--          where app = bootstrap @TextPlain usersApi & compile & toApp id
--   
data Endpoint (m :: * -> *) a Endpoint :: (Input -> EndpointResult m a) -> String -> Endpoint a [runEndpoint] :: Endpoint a -> Input -> EndpointResult m a [toString] :: Endpoint a -> String -- | Inversed alias for mapOutputM (~>) :: Monad m => Endpoint m a -> (a -> m (Output b)) -> Endpoint m b infixl 0 ~> -- | Advanced version of ~> operator that allows to map -- Endpoint m (HList ls) over a function of arity N equal to N -- elements of HList. General rule of thumb when to use this operator is -- whenever there is an HList on the left side. (~>>) :: (Monad m, FnToProduct fn ls (m (Output b))) => Endpoint m (HList ls) -> fn -> Endpoint m b infixl 0 ~>> -- | Create product of two Endpoints that sequentially match a -- request and values are adjoined into HList. If some of -- endpoints doesn't match a request, the final result is also -- non-matching (//) :: (MonadCatch m, AdjoinHList (a : (b : '[])) out) => Endpoint m a -> Endpoint m b -> Endpoint m (HList out) infixr 2 // -- | Create new Endpoint of two endpoints, adjoining values into -- Coproduct During request resolution the following logic is -- applied: -- -- (|+|) :: forall m a b out. (MonadCatch m, AdjoinCoproduct (Coproduct a (Coproduct b CNil)) out) => Endpoint m a -> Endpoint m b -> Endpoint m out infixl 2 |+| -- | Turn endpoint into one that matches only for GET requests get :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for POST requests post :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for PUT requests put :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for PATCH requests patch :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for DELETE requests delete :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for HEAD requests head' :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for TRACE requests trace' :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for CONNECT requests connect :: Endpoint m a -> Endpoint m a -- | Turn endpoint into one that matches only for OPTIONS requests options :: Endpoint m a -> Endpoint m a -- | Endpoint that tries to decode head of the current path reminder into -- specific type. It consumes head of the reminder. -- -- path :: forall a m. (DecodePath a, Applicative m, Typeable a) => Endpoint m a -- | Endpoint that matches any path and discards reminder pathAny :: Applicative m => Endpoint m (HList '[]) -- | Endpoint that matches only if the head of current path reminder is -- equal to some given constant value. It consumes head of the reminder. -- -- pathConst :: Applicative m => Text -> Endpoint m (HList '[]) -- | Short alias for pathConst p' :: Applicative m => Text -> Endpoint m (HList '[]) -- | Endpoint that matches only against empty path reminder pathEmpty :: Applicative m => Endpoint m (HList '[]) -- | Endpoint that consumes the rest of the path reminder and decode it -- using provided DecodePath for some type a paths :: forall a m. (DecodePath a, Applicative m, Typeable a) => Endpoint m [a] -- | Endpoint that tries to decode parameter name from the request -- query string. Always matches, but may throw an exception in case: -- -- param :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode parameter name from the request -- query string. Always matches, but may throw an exception in case: -- -- paramMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) -- | Endpoint that tries to decode all parameters name from the -- request query string. Always matches, but may throw an exception in -- case: -- -- params :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m [a] -- | Endpoint that tries to decode all parameters name from the -- request query string. Always matches, but may throw an exception in -- case: -- -- paramsNel :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (NonEmpty a) -- | Endpoint that tries to decode body of request into some type -- a using corresponding Decode instance. Matches if body -- isn't chunked. May throw an exception in case: -- -- body :: forall ct a m. (Decode ct a, MonadIO m, MonadThrow m) => Endpoint m a -- | Endpoint that tries to decode body of request into some type -- a using corresponding Decode instance. Matches if body -- isn't chunked. May throw an exception in case: -- -- bodyMaybe :: forall ct a m. (Decode ct a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) -- | Alias for body @TextPlain textBody :: (Decode TextPlain a, MonadIO m, MonadThrow m) => Endpoint m a -- | Alias for bodyMaybe @TextPlain textBodyMaybe :: (Decode TextPlain a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) -- | Alias for body @ApplicationJson jsonBody :: (Decode ApplicationJson a, MonadIO m, MonadThrow m) => Endpoint m a -- | Alias for bodyMaybe @ApplicationJson jsonBodyMaybe :: (Decode ApplicationJson a, MonadIO m, MonadThrow m) => Endpoint m (Maybe a) -- | Endpoint that tries to decode cookie name from a request. -- Always matches, but may throw an exception in case: -- -- cookie :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode cookie name from a request. -- Always matches, but may throw an exception in case: -- -- cookieMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) -- | Endpoint that tries to decode header name from a request. -- Always matches, but may throw an exception in case: -- -- header :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m a -- | Endpoint that tries to decode header name from a request. -- Always matches, but may throw an exception in case: -- -- headerMaybe :: forall a m. (DecodeEntity a, MonadThrow m) => ByteString -> Endpoint m (Maybe a) -- | Encoding of some type a into payload of HTTP response Phantom -- type ct guarantees that compiler checks support of encoding -- of some a into content of given Content-Type by -- looking for specific Encode instance. class Encode (ct :: Symbol) a encode :: Encode ct a => a -> ByteString -- | Decoding of HTTP request payload into some type a. Phantom -- type ct guarantees that compiler checks support of decoding -- some a from content of given Content-Type by looking -- for specific Decode instance. class Decode (ct :: Symbol) a decode :: Decode ct a => ByteString -> Either LinnetError a -- | Output of Endpoint that carries some Payload -- a together with response status and headers data Output a Output :: Status -> Payload a -> [Header] -> Output a [outputStatus] :: Output a -> Status [outputPayload] :: Output a -> Payload a [outputHeaders] :: Output a -> [Header] -- | Create Output with Payload a and status OK -- 200 ok :: a -> Output a -- | Create Output with Payload a and status -- CREATED 201 created :: a -> Output a -- | Create Output with NoPayload and status ACCEPTED -- 202 accepted :: Output a -- | Create Output with NoPayload and status NO CONTENT -- 202 noContent :: Output a -- | Create Output with ErrorPayload e and status BAD -- REQUEST 400 badRequest :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- UNAUTHORIZED 401 unauthorized :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- PAYMENT REQUIRED 402 paymentRequired :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- FORBIDDEN 403 forbidden :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status NOT -- FOUND 404 notFound :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- METHOD NOT ALLOWED 405 methodNotAllowed :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status NOT -- ACCEPTABLE 406 notAcceptable :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- CONFLICT 409 conflict :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status GONE -- 410 gone :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- LENGTH REQUIRED 411 lengthRequired :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- PRECONDITIONED FAILED 412 preconditionFailed :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- REQUEST ENTITY TOO LARGE 413 requestEntityTooLarge :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- UNPROCESSABLE ENTITY 422 unprocessableEntity :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status TOO -- MANY REQUESTS 422 tooManyRequests :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- INTERNAL SERVER ERROR 500 internalServerError :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status NOT -- IMPLEMENTED 501 notImplemented :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status BAD -- GATEWAY 502 badGateway :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- SERVICE UNAVAILABLE 503 serviceUnavailable :: Exception e => e -> Output a -- | Create Output with ErrorPayload e and status -- GATEWAY TIMEOUT 504 gatewayTimeout :: Exception e => e -> Output a -- | Create Bootstrap out of single Endpoint and some given -- Content-Type: -- --
--   bootstrap @TextPlain (pure "foo")
--   
bootstrap :: forall (ct :: Symbol) m a. Endpoint m a -> Bootstrap m (Coproduct (Proxy ct) CNil) (HList '[Endpoint m a]) -- | Add another endpoint to Bootstrap for purpose of serving -- multiple Content-Types with *different* endpoints -- --
--   bootstrap @TextPlain (pure "foo") & server @ApplicationJson (pure "bar")
--   
serve :: forall (ct :: Symbol) cts es m a. Endpoint m a -> Bootstrap m cts (HList es) -> Bootstrap m (Coproduct (Proxy ct) cts) (HList (Endpoint m a : es)) -- | Compile Bootstrap into ReaderT Request m Response for -- further combinations. Might be useful to implement middleware in -- context of the same monad m: -- --
--   bootstrap @TextPlain (pure "foo") & compile
--   
compile :: forall cts m es. Compile cts m es => Bootstrap m cts es -> ReaderT Request m Response -- | Convert ReaderT Request m Response into WAI -- Application -- --
--   bootstrap @TextPlain (pure "foo") & compile & toApp id
--   
-- -- The first parameter here is a natural transformation of -- Endpoints monad m into IO. In case if -- selected monad is IO already then id is just enough. -- Otherwise, it's a good place to define how to "start" custom monad for -- each request to come and convert it to IO. -- -- As an example: -- -- toApp :: (forall a. m a -> IO a) -> ReaderT Request m Response -> Application -- | Run an Application on the given port. This calls -- runSettings with defaultSettings. run :: Port -> Application -> IO () -- | Content-Type literal for application/json encoding type ApplicationJson = "application/json" -- | Content-Type literal for text/html encoding type TextHtml = "text/html" -- | Content-Type literal for text/plain encoding type TextPlain = "text/plain"