Safe Haskell | Safe-Inferred |
---|---|
Language | GHC2021 |
Main module to write servers
Server is a function from response (Resp) to request (Req). Request is wrapped into monad. Library supports IO-monad and ReaderT over IO like monads.
We can build servers from parts with flexible combinators. Let's build hello-world server:
main :: IO () main = runServer 8080 server server :: Server IO server = "api" /. "v1" /. "hello" /. Get @Text handleHello handleHello :: IO Text handleHello = pure "Hello World"
We can iuse monoids to combine servers and newtype-wrappers to read various inputs. See readme of the repo for tutorial and docs.
Synopsis
- newtype Server m = Server {}
- data Json
- newtype Get ty m a = Get (m a)
- newtype Post ty m a = Post (m a)
- newtype Put ty m a = Put (m a)
- newtype Delete ty m a = Delete (m a)
- newtype Patch ty m a = Patch (m a)
- newtype Options ty m a = Options (m a)
- (/.) :: ToServer a => Text -> a -> Server (ServerMonad a)
- newtype Capture a = Capture a
- newtype Query (sym :: Symbol) a = Query a
- newtype Optional (sym :: Symbol) a = Optional (Maybe a)
- newtype Body a = Body a
- newtype RawBody = RawBody ByteString
- newtype Header (sym :: Symbol) = Header (Maybe ByteString)
- newtype RawFormData = RawFormData FormBody
- data FormBody = FormBody {
- params :: [(ByteString, ByteString)]
- files :: [(ByteString, FileInfo ByteString)]
- newtype FormJson a = FormJson a
- newtype PathInfo = PathInfo [Text]
- data AddHeaders a = AddHeaders {
- headers :: ResponseHeaders
- content :: a
- data SetStatus a = SetStatus {}
- setStatus :: Monad m => Status -> Server m -> Server m
- addHeaders :: Monad m => ResponseHeaders -> Server m -> Server m
- data Error a = Error {}
- handleError :: (Exception a, MonadCatch m) => (a -> Server m) -> Server m -> Server m
- runServer :: Int -> Server IO -> IO ()
- data ServerConfig = ServerConfig {}
- toApplication :: ServerConfig -> Server IO -> Application
- class Monad m => HasServer m where
- type ServerResult m :: Type
- renderServer :: Server m -> ServerResult m
- fromReader :: env -> Server (ReaderT env IO) -> IO (Server IO)
- class ToTextResp a where
- toTextResp :: a -> Resp
- class ToJsonResp a where
- toJsonResp :: a -> Resp
- class ToHtmlResp a where
- toHtmlResp :: a -> Resp
- class FromText a where
- class ToText a where
- badRequest :: Text -> Resp
- class Monad (ServerMonad a) => ToServer a where
- type ServerMonad a :: Type -> Type
- toServer :: a -> Server (ServerMonad a)
- withServerAction :: Monad m => Server m -> m () -> Server m
- module Network.HTTP.Types.Status
types
Server type. It is a function fron request to response. Some servers does not return valid value. We use it to find right path.
Example:
server :: Server IO server = "api" /. "v1" /. mconcat [ "foo" /. (\(Query @"name" arg) -> Get @Json (handleFoo arg) , "bar" /. Post @Json handleBar ] handleFoo :: Int -> IO Text handleBar :: IO Text
Note that server is monoid and it can be constructed with Monoid functions and
path constructor (/.)
. To pass inputs for handler we can use special newtype wrappers:
Query
- for required query parametersOptional
- for optional query parametersCapture
- for parsing elements of URIBody
- fot JSON-body inputRawBody
- for raw ByteString inputHeader
- for headers
To distinguish by HTTP-method we use corresponding constructors: Get, Post, Put, etc. Let's discuss the structure of the constructor. Let's take Get for example:
newtype Get ty m a = Get (m a)
Let's look at the arguments of he type
ty
- type of the response. it can be: Text, Html, Json, ByteStringm
- underlying server monada
- result type. It should be convertible to the type of the response.
also result can be wrapped to special data types to modify Http-response. we have wrappers:
SetStatus
- to set statusAddHeaders
- to append headersEither (Error err)
- to response with errors
DSL
Type tag of Json-response.
Instances
(Monad m, ToJSON a) => ToServer (Delete Json m a) Source # | |
(Monad m, ToJSON a) => ToServer (Get Json m a) Source # | |
(Monad m, ToJSON a) => ToServer (Options Json m a) Source # | |
(Monad m, ToJSON a) => ToServer (Patch Json m a) Source # | |
(Monad m, ToJSON a) => ToServer (Post Json m a) Source # | |
(Monad m, ToJSON a) => ToServer (Put Json m a) Source # | |
type ServerMonad (Delete Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Get Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Options Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Patch Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Post Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Put Json m a) Source # | |
Defined in Mig |
methods
Get method. Note that we can not use body input with Get-method, use Post for that. So with Get we can use only URI inputs (Query, Optional, Capture)
Get (m a) |
Instances
(Monad m, ToHtmlResp a) => ToServer (Get Html m a) Source # | |
Monad m => ToServer (Get ByteString m ByteString) Source # | |
Defined in Mig type ServerMonad (Get ByteString m ByteString) :: Type -> Type Source # toServer :: Get ByteString m ByteString -> Server (ServerMonad (Get ByteString m ByteString)) Source # | |
Monad m => ToServer (Get ByteString m ByteString) Source # | |
Defined in Mig type ServerMonad (Get ByteString m ByteString) :: Type -> Type Source # toServer :: Get ByteString m ByteString -> Server (ServerMonad (Get ByteString m ByteString)) Source # | |
(Monad m, ToJSON a) => ToServer (Get Json m a) Source # | |
(Monad m, ToTextResp a) => ToServer (Get Text m a) Source # | |
type ServerMonad (Get Html m a) Source # | |
Defined in Mig | |
type ServerMonad (Get ByteString m ByteString) Source # | |
Defined in Mig | |
type ServerMonad (Get ByteString m ByteString) Source # | |
Defined in Mig | |
type ServerMonad (Get Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Get Text m a) Source # | |
Defined in Mig |
Post method
Post (m a) |
Instances
(Monad m, ToHtmlResp a) => ToServer (Post Html m a) Source # | |
(Monad m, ToJSON a) => ToServer (Post Json m a) Source # | |
(Monad m, ToTextResp a) => ToServer (Post Text m a) Source # | |
type ServerMonad (Post Html m a) Source # | |
Defined in Mig | |
type ServerMonad (Post Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Post Text m a) Source # | |
Defined in Mig |
Put method
Put (m a) |
Instances
(Monad m, ToHtmlResp a) => ToServer (Put Html m a) Source # | |
(Monad m, ToJSON a) => ToServer (Put Json m a) Source # | |
(Monad m, ToTextResp a) => ToServer (Put Text m a) Source # | |
type ServerMonad (Put Html m a) Source # | |
Defined in Mig | |
type ServerMonad (Put Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Put Text m a) Source # | |
Defined in Mig |
newtype Delete ty m a Source #
Delete method
Delete (m a) |
Instances
(Monad m, ToHtmlResp a) => ToServer (Delete Html m a) Source # | |
(Monad m, ToJSON a) => ToServer (Delete Json m a) Source # | |
(Monad m, ToTextResp a) => ToServer (Delete Text m a) Source # | |
type ServerMonad (Delete Html m a) Source # | |
Defined in Mig | |
type ServerMonad (Delete Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Delete Text m a) Source # | |
Defined in Mig |
Patch method
Patch (m a) |
Instances
(Monad m, ToHtmlResp a) => ToServer (Patch Html m a) Source # | |
(Monad m, ToJSON a) => ToServer (Patch Json m a) Source # | |
(Monad m, ToTextResp a) => ToServer (Patch Text m a) Source # | |
type ServerMonad (Patch Html m a) Source # | |
Defined in Mig | |
type ServerMonad (Patch Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Patch Text m a) Source # | |
Defined in Mig |
newtype Options ty m a Source #
Options method
Options (m a) |
Instances
(Monad m, ToHtmlResp a) => ToServer (Options Html m a) Source # | |
(Monad m, ToJSON a) => ToServer (Options Json m a) Source # | |
(Monad m, ToTextResp a) => ToServer (Options Text m a) Source # | |
type ServerMonad (Options Html m a) Source # | |
Defined in Mig | |
type ServerMonad (Options Json m a) Source # | |
Defined in Mig | |
type ServerMonad (Options Text m a) Source # | |
Defined in Mig |
path and query
Build API for routes with queries and captures. Use monoid to combine several routes together.
(/.) :: ToServer a => Text -> a -> Server (ServerMonad a) infixr 4 Source #
Path constructor (right associative). Example:
server :: Server IO server = "api" /. "v1" /. mconcat [ "foo" /. Get @Json handleFoo , "bar" /. Post @Json handleBar ] handleFoo, handleBar :: IO Text
Capture a |
newtype Query (sym :: Symbol) a Source #
Mandatary query parameter. Name is encoded as type-level string. Example:
"api" /. handleFoo handleFoo :: Query "name" Int -> Server IO handleFoo (Query arg) = ...
Query a |
newtype Optional (sym :: Symbol) a Source #
Optional query parameter. Name is encoded as type-level string. Example:
"api" /. handleFoo handleFoo :: Optional "name" -> Server IO handleFoo (Optional maybeArg) = ...
Reads Json body (lazy). We can limit the body size with server config. Example:
"api" /. "search" /. (\(Body request) -> handleSearch request)
Body a |
Reads raw body as lazy bytestring. We can limit the body size with server config. Example:
"api" /. "upload" /. (\(RawBody content) -> handleUpload content)
newtype Header (sym :: Symbol) Source #
Reads input header. Example:
"api" /. (\(Header @"Trace-Id" traceId) -> Post @Json (handleFoo traceId)) handleFoo :: Maybe ByteString -> IO FooResponse
newtype RawFormData Source #
Parse raw form body. It includes named form arguments and file info. Note that we can not use FormBody and JSON-body at the same time. They occupy the same field in the HTTP-request.
Instances
(ToServer b, MonadIO (ServerMonad b)) => ToServer (RawFormData -> b) Source # | |
Defined in Mig type ServerMonad (RawFormData -> b) :: Type -> Type Source # toServer :: (RawFormData -> b) -> Server (ServerMonad (RawFormData -> b)) Source # | |
type ServerMonad (RawFormData -> b) Source # | |
Defined in Mig |
FormBody | |
|
It reads form as plain JSON-object where name of the form's field becomes a field of JSON-object and every value is Text.
For example if submit a form with fields: name, password, date. We can read it in the data type:
data User = User { name :: Text , passord :: Text , date :: Text }
Note that we can not use FormBody and JSON-body at the same time. They occupy the same field in the HTTP-request.
FormJson a |
Reads current path info
response
How to modify response and attach specific info to it
data AddHeaders a Source #
Attach headers to response. It can be used inside any ToXxxResp value. Example:
"api" /. handleFoo handleFoo :: Get Text IO (AddHeaders Text) handleFoo = Get $ pure $ AddHeaders headers "Hello foo"
AddHeaders | |
|
Instances
ToHtmlResp a => ToHtmlResp (AddHeaders a) Source # | |
Defined in Mig toHtmlResp :: AddHeaders a -> Resp Source # | |
ToJsonResp a => ToJsonResp (AddHeaders a) Source # | |
Defined in Mig toJsonResp :: AddHeaders a -> Resp Source # | |
ToTextResp a => ToTextResp (AddHeaders a) Source # | |
Defined in Mig toTextResp :: AddHeaders a -> Resp Source # |
Set status to response. It can be ised inside any ToXxxResp value. Example:
"api" /. handleFoo handleFoo :: Get Text IO (SetStatus Text) handleFoo = Get $ pure $ SetStatus status500 "Bad request"
Instances
ToHtmlResp a => ToHtmlResp (SetStatus a) Source # | |
ToJsonResp a => ToJsonResp (SetStatus a) Source # | |
ToTextResp a => ToTextResp (SetStatus a) Source # | |
setStatus :: Monad m => Status -> Server m -> Server m Source #
Sets status for response of the server
addHeaders :: Monad m => ResponseHeaders -> Server m -> Server m Source #
Adds headers for response of the server
Errors
How to report errors
Errors
Instances
(Typeable a, Show a) => Exception (Error a) Source # | |
Defined in Mig.Internal.Types toException :: Error a -> SomeException # fromException :: SomeException -> Maybe (Error a) # displayException :: Error a -> String # | |
Show a => Show (Error a) Source # | |
(Show a, Typeable a) => HasServer (ReaderT env (ExceptT (Error a) IO)) Source # | |
(ToJSON err, ToHtmlResp a) => ToHtmlResp (Either (Error err) a) Source # | |
(ToJSON err, ToJsonResp a) => ToJsonResp (Either (Error err) a) Source # | |
(ToText err, ToTextResp a) => ToTextResp (Either (Error err) a) Source # | |
type ServerResult (ReaderT env (ExceptT (Error a) IO)) Source # | |
handleError :: (Exception a, MonadCatch m) => (a -> Server m) -> Server m -> Server m Source #
Handle errors
Run
Run server application
toApplication :: ServerConfig -> Server IO -> Application Source #
Convert server to WAI-application
Render
Render Reader-IO monad servers to IO servers.
class Monad m => HasServer m where Source #
Class contains types which can be converted to IO-based server to run as with WAI-interface.
We can run plain IO-servers and ReaderT over IO based servers. Readers can be wrapped in newtypes.
In that case we can derive automatically HasServer
instance.
type ServerResult m :: Type Source #
renderServer :: Server m -> ServerResult m Source #
fromReader :: env -> Server (ReaderT env IO) -> IO (Server IO) Source #
Render reader server to IO-based server
Convertes
class ToTextResp a where Source #
Values convertible to Text (lazy)
toTextResp :: a -> Resp Source #
Instances
ToTextResp Text Source # | |
ToTextResp Text Source # | |
ToTextResp Int Source # | |
ToTextResp a => ToTextResp (AddHeaders a) Source # | |
Defined in Mig toTextResp :: AddHeaders a -> Resp Source # | |
ToTextResp a => ToTextResp (SetStatus a) Source # | |
(ToText err, ToTextResp a) => ToTextResp (Either (Error err) a) Source # | |
class ToJsonResp a where Source #
Values convertible to Json
toJsonResp :: a -> Resp Source #
Instances
ToJSON a => ToJsonResp a Source # | |
Defined in Mig toJsonResp :: a -> Resp Source # | |
ToJsonResp a => ToJsonResp (AddHeaders a) Source # | |
Defined in Mig toJsonResp :: AddHeaders a -> Resp Source # | |
ToJsonResp a => ToJsonResp (SetStatus a) Source # | |
(ToJSON err, ToJsonResp a) => ToJsonResp (Either (Error err) a) Source # | |
class ToHtmlResp a where Source #
Values convertible to Html
toHtmlResp :: a -> Resp Source #
Instances
ToMarkup a => ToHtmlResp a Source # | |
Defined in Mig toHtmlResp :: a -> Resp Source # | |
ToHtmlResp a => ToHtmlResp (AddHeaders a) Source # | |
Defined in Mig toHtmlResp :: AddHeaders a -> Resp Source # | |
ToHtmlResp a => ToHtmlResp (SetStatus a) Source # | |
(ToJSON err, ToHtmlResp a) => ToHtmlResp (Either (Error err) a) Source # | |
class FromText a where Source #
Aything convertible from text
Values convertible to lazy text
utils
badRequest :: Text -> Resp Source #
Bad request response
class Monad (ServerMonad a) => ToServer a where Source #
Class ToServer contains anything convertible to Server m
. We use it for flexuble composition
of servers from functions with arbitrary number of arguments. Arguments can
be of specific types: Query, Body, Optional, Capture, Header, etc.
We use type-level strings to encode query-names.
Example:
"api" /. "foo" /. (\(Query @"argA" argA) (Optional @"argB" argB) (Body jsonRequest) -> Post @Json $ handleFoo argA argB jsonRequest) handleFoo :: Int -> Maybe Text -> FooRequest -> IO FooResponse handleFoo = ...
Note that we can use any amount of arguments. And type of the input is decoded fron newtype wrapper which is used with argument of the handler function.
Also we can return pure errors with Either. Anything which can be returned from function
can be wrapped to Either (Error err)
.
For example in previous case we can use function which returns errors as values:
type ServerError = Error Text handleFoo :: Int -> Maybe Text -> FooRequest -> IO (Either ServerError FooResponse) handleFoo = ...
the result of error response is automatically matched with normal response of the server and standard Error type lets us pass status to response and some details.
type ServerMonad a :: Type -> Type Source #
toServer :: a -> Server (ServerMonad a) Source #
Instances
module Network.HTTP.Types.Status