Skip to content

Commit

Permalink
Merge pull request #62 from anton-k/update-docs
Browse files Browse the repository at this point in the history
Update docs
  • Loading branch information
anton-k authored Nov 5, 2023
2 parents d984713 + 966ea85 commit 27353ad
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 70 deletions.
26 changes: 12 additions & 14 deletions docs/src/00-foreword.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
# Mig by example

Mig is a lightweight and easy to use library to build servers in Haskell.
It is sort of servant for Simple/Boring Haskell.
Mig is a lightweight and easy to use library to build HTTP servers and clients in Haskell.
It is kind of servant for Simple/Boring Haskell.
This book is an example driven guide to the library.
The name `mig` (pronounced as meeg) is a russian word for "instant moment".

The main features are:
The main features of the mig library are:

* lightweight library

* easy to use. It has simple design on purpose

* expressive DSL to compose servers

* type-safe route handlers and conversions

* handlers are encoded with generic Haskell functions

* built on top of WAI and warp server libraries.

* provides Swagger to your server with one-line of code

* relies on standard classes to compose servers. The server is a monoid
* we can build HTTP-clients from the server definition

Example of hello world server:

Expand Down Expand Up @@ -96,9 +91,9 @@ But it is akin to servant in usage of type-safe conversions and type-level safet

### servant

The mig uses the same ideas of type-safe handlers which a re based on generic Haskell functions.
The mig uses the same ideas of type-safe handlers which are based on generic Haskell functions.
The main difference is that in servant the whole server is described as type.
Which leads to type-safety and ability to derive API schema, from the type.
Which leads to type-safety and ability to derive API schema from the type.

But downside of it is fancy big types and very advanced concepts that user needs to know
in order to use the library. Also one drawback to me is when things go wrong and you get
Expand All @@ -115,8 +110,8 @@ Using type-level description of the routes provide the same benefits as in serva

* safe type check of the conversions of low level request and response elements
* usage of generic Haskell functions as handlers

* declarative design of the servers
* composition of servers from small sub-servers

In the mig API is a value that is derived from the server at run-time.
It allows us to build clients and OpenApi swagger too.
Expand All @@ -127,9 +122,12 @@ something more simple.
### scotty

The scotty is also in domain of simple, easy to use solutions.
so why did I wrote mig and haven't used the scotty instead?
So why did I wrote mig and haven't used the scotty instead?
Scotty features more imperative approach where you write handlers as
expression for Scotty library monad. But it does not looks so well as in servant's case to me.
It is harder to assemble servers from parts. And I really like the idea of type-safe
conversions of various parts of request and response.

So the scotty is simple enough but for me it lacks some servant features
such as composability of the servers (nice tree structure of the API)
and type-safe conversions of various parts of request and response.
47 changes: 35 additions & 12 deletions docs/src/01-hello-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Let's build hello world application.
We are going to build simple JSON API server with single route which replies
with constant text to request
with constant text to request.

We have installed the library `mig-server`. Let's import the main module.
It brings into the scope all main functions of the library:
Expand All @@ -24,6 +24,9 @@ hello = undefined
```

So we serve single route with path `"api/v1/hello"`.
This example relies on extension `OverloadedStrings` to convert
string literals to values of `Path` type. Usually I add it in the cabal file of
the project.
Let's cover the types first.

### The server type
Expand All @@ -36,7 +39,8 @@ newtype Server m = Server (Api (Route m))
```

The `Api` type is a value to describe the API schema and `Route` contains
useful info on the type of the route (method, description of the inputs and outputs).
useful info on the type of the route (method, description of the inputs and outputs)
and how to run the handler function.
The server is parametrized by some monad type. For this example we use `IO`-monad.
It means that all our handlers are going to return `IO`-values.

Expand All @@ -45,7 +49,7 @@ It means that all our handlers are going to return `IO`-values.
To bind path "api/v1/hello" to handler `hello` we use function `(/.)`. Let's look at it's type signature:

```haskell
(/.) :: (ToServer a) => Path -> a -> Server (MonadOf a)
(/.) :: ToServer a => Path -> a -> Server (MonadOf a)
```

It expects the `Path` which has instance of class `IsString` that is why we can
Expand All @@ -56,7 +60,7 @@ We have special class called `ToServer` which can convert many different types t
The output type is a bit tricky: `Server (MonadOf a)`.
The `MonadOf` is a type function which can extract `m` from `(Server m)`.
Or for example it can extract `m` from the function `request -> m response`.
So the `MonadOf` is a way to get underlying server monad from any value.
So the `MonadOf` is a way to get underlying server monad from any type.

Let's be more specific and study our example.
The type of the handler is `Get IO (Resp Text)`
Expand All @@ -77,8 +81,8 @@ hello :: Get IO (Resp Json Text)
| | | | |
| | | | +-- response body converted to byte string
| | | |
| | | +---- codec to convert it
| | | (the media-type route uses for response body)
| | | +---- codec to convert result to response body
| | | (the media-type which the route uses for response body)
| | |
| | +---- type of response which holds HTTP-response info with result
| |
Expand All @@ -99,7 +103,7 @@ The type `Send` is just a wrapper on top of monadic value:
newtype Send method m a = Send (m a)
```

It encodes HTTP-method on type level. This is useful to aggregate value for API-schema of our server.
It encodes HTTP-method on type level as so called phantom type. This is useful to aggregate value for API-schema of our server.
We have type synonyms for all HTTP-methods (`Get`, `Post`, `Put` etc).

It's interesting to know that library mig does not use any custom monads for operation.
Expand Down Expand Up @@ -145,7 +149,7 @@ Let's complete the example and define a handler which returns static text:

```haskell
hello :: Get IO (Resp Json)
hello = pure $ ok "Hello World!"
hello = Send $ pure $ ok "Hello World!"
```

We have several wrappers here:
Expand All @@ -154,6 +158,14 @@ We have several wrappers here:
* `pure` - converts pure value to IO-based value
* `Send` - send converts monadic value to server. It adds information on HTTP-method of the return type.

As `Send` is also monad if `m` is a monad we can write this definition
a bit shorter and omit the `Send` constructor:

```haskell
hello :: Get IO (Resp Json)
hello = pure $ ok "Hello World!"
```

### Run a server

Let's run the server with warp. For that we define the `main` function for our application:
Expand Down Expand Up @@ -312,6 +324,17 @@ server =
]
```

Also for example with paths for alternatives in the list we can omit `toServer` too:

```haskell
server =
"api/v1" /.
[ "hello" /. hello
, "bye" /. bye
]
```


### The path type

Let's discuss the `Path` type.
Expand All @@ -331,8 +354,8 @@ data PathItem
```

The static path item is a rigid entity with exact match to string.
We used it in all our examples so far.
but capture is wild-card that is going to be used as input to the handler.
We have used it in all our examples so far.
But capture is wild-card which is going to be used as input to the handler.

To construct only rigid paths we can use strings:

Expand All @@ -341,13 +364,13 @@ To construct only rigid paths we can use strings:
"foo/bar"
```
To get captures we use `*`-wildcard:
To specify captures we use `*`-wildcard:
```
api/v2/*/get
```
In the star request captures any text. There might be as many stars
In the star mark the request captures any text. There might be as many stars
in the path as you wish. But they should be supported by the handler.
We will touch upon that later.
Expand Down
8 changes: 4 additions & 4 deletions docs/src/02-request-anatomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ We have several types of inputs in HTTP:
right into it: `api/get/route/someCaptureValueA/someCaptureValueB`

* header parameters. They are in HTTP-request headers. For example header that
reports media-type of the request body: "Content-Type: application/json"
reports media-type of the request body: `"Content-Type: application/json"`

* request body. It is a value packed into HTTP-request. It can be JSON or text or raw string
or XML. All sorts of things can be used as request bodies.
Expand Down Expand Up @@ -377,7 +377,7 @@ Making `curl` request can quickly become hard to manage as
our servers become more complicated. There is OpenAPI standard
that defines how to describe HTTP-server API. Also it provides
Swagger. It is a tool to make it easy to check how server behaves.
It provides an HTTP-client for the server which allows us to
It provides an HTTP-client for the server usable from the browser as plain web-page which allows us to
query server routes.

Let's add a swagger to our server. Just add this line:
Expand All @@ -400,7 +400,7 @@ We can add swagger to any server with function:
withSwagger :: SwaggerConfig m -> Server m -> Server m
```

We will study the `ServerConfig` in details in one of the next chapters
We will study the `SwaggerConfig` in details in one of the next chapters
but for now the default value which is set with `def` from library `data-default`
is fine.

Expand All @@ -410,7 +410,7 @@ We can look at the request and response data with tracing functions
which come from library `mig-extra` from the module `Mig.Extra.Plugin.Trace`:

```haskell
data Verbosity = V0 | V1 | V2 | V3
data Verbosity = V0 | V1 | V2 | V3

-- log http requests and responses
logHttp :: Verbosity -> Plugin m
Expand Down
38 changes: 28 additions & 10 deletions docs/src/03-response-anatomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type than the type of the result.

### Response type class `IsResp`

To unify the output we have special type class called `IsResp` for
To unify the output for both cases of `Resp` and `RespOr` we have special type class called `IsResp` for
all types which can be converted to low-level HTTP-response type `Response`.

Let's study this type class.
Expand All @@ -64,6 +64,7 @@ It has two associated types for the type of the body (`RespBody`) and type of th
class IsResp a where
type RespBody a :: Type
type RespError a :: Type
type RespMedia a :: Type
```

We can return successful result with method `ok`:
Expand All @@ -80,7 +81,7 @@ When things go bad we can report error with method `bad`:
bad :: Status -> RespError a -> a
```

Sometimes at rare cases we do not what to return any content from response.
Sometimes we do not want to return any content from response.
We can just report error status and leave the body empty:

```haskell
Expand All @@ -103,6 +104,14 @@ we would like set it explicitly. For that we have the method:
setMedia :: MediaType -> a -> a
```

Also we can set response status with function:

```haskell
-- | Set the response status
setStatus :: Status -> a -> a
```


Also the core of the class is the method to convert value to low-level response:

```haskell
Expand All @@ -112,7 +121,7 @@ Also the core of the class is the method to convert value to low-level response:

Both `Resp` and `RespOr` are instances of `IsResp` class and
we can `Send` as HTTP-response anything which has instance of `IsResp`.
For now there are only three types. The third one is the low-level `Response`.
For now there are only three types. The third one instance is the low-level `Response` type.

## Examples

Expand All @@ -124,7 +133,7 @@ and we use `RespOr` if handler can produce and error.
We already have seen many usages of `Resp` type. Let's define something
that can produce an error. Let's define server that calculates
square root of the value. For negative numbers it is not defined in the
realm of real numbers. So let's define the handler that use `RespOr` type:
domain of real numbers. So let's define the handler that use `RespOr` type:

```haskell
import Mig.Json.IO
Expand Down Expand Up @@ -188,7 +197,7 @@ for successful response and all functions that need the status take it as argume

### How it works with server definition

How we can use both of the types as responses: `Resp` and `RespOr`.
How can we use both of the types as responses: `Resp` and `RespOr`?
Recall that `/.` function is overloaded by the second argument and
we have a rule for `ToServer` class that:

Expand All @@ -207,17 +216,26 @@ We have learned that there are only tow types to return from server handler:
The need for the second type is to have different type of the error
and different for the result. If both error and result have the same
type then we can use `Resp`. This is common case for HTML servers when we
return HTML-page as result. In case of error we would like to show the page too
as in case of success. The difference would be in the HTTP-status of the response.
return HTML-page as a result. In the case of error we would like to show the page too
as in the case of success. The difference would be in the HTTP-status of the response.

And this goes well with `IsResp` class as for `Resp media a` error type `RespError`
equals to `a` as the value for `RespBody` too.

Also we have learned various methods of the `IsResp` class and how they
can be useful in server definitions.

With this chapter we have covered both requests and responses and which types the can
have. See the source code [`RouteArgs`](https://github.com/anton-k/mig/blob/main/examples/mig-example-apps/RouteArgs/Main.hs)
See the source code [`RouteArgs`](https://github.com/anton-k/mig/blob/main/examples/mig-example-apps/RouteArgs/Main.hs)
for examples on the topic that we have just studied.

With this chapter we have covered both requests and responses and which types the can
have.
It covers all basics of the mig library. You are now well equipped to build
HTTP-servers in Haskell. The rest of the tutorial covers more advanced features of the library:

* how to use custom monads. So far we used only plain `IO`-monad
* how to use plugins/middlewares to add common procedures to all handlers of the server
* how to create HTTP-clients from servers
* description of two more substantial examples
* JSON API application for weather forecast
* HTML example for blogpost site

Loading

0 comments on commit 27353ad

Please sign in to comment.