Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/haskell-to-elm/servant-to-elm

Generate Elm client libraries from Servant API definitions.
https://github.com/haskell-to-elm/servant-to-elm

Last synced: about 2 months ago
JSON representation

Generate Elm client libraries from Servant API definitions.

Awesome Lists containing this project

README

        

# servant-to-elm [![Hackage](https://img.shields.io/hackage/v/servant-to-elm.svg)](https://hackage.haskell.org/package/servant-to-elm)

This is a library for generating Elm client libraries from Servant API
definitions.

See [haskell-to-elm](https://github.com/folq/haskell-to-elm) for background
information and a more elaborate motivation.

## Basic usage

Given a Servant API like the following

```haskell
type UserAPI
= "user" :> Get '[JSON] User
:<|> "user" :> ReqBody '[JSON] User :> PostNoContent '[JSON] NoContent
```

we can generate the Elm code for making requests against it as follows:

```haskell
main :: IO ()
main = do
let
definitions =
map (elmEndpointDefinition "Config.urlBase" ["Api"]) (elmEndpoints @UserAPI)
<> jsonDefinitions @User

modules =
Pretty.modules $
Simplification.simplifyDefinition <$> definitions

forM_ (HashMap.toList modules) $ \(_moduleName, contents) ->
print contents
```

Running `main` prints:

```elm
module Api exposing (..)

import Api.User
import Config
import Http
import Json.Decode

getUser : Cmd (Result (Http.Error , Maybe { metadata : Http.Metadata
, body : String }) Api.User.User)
getUser =
Http.request { method = "GET"
, headers = []
, url = Config.urlBase ++ "/user"
, body = Http.emptyBody
, expect = Http.expectStringResponse identity (\a -> case a of
Http.BadUrl_ b ->
Err (Http.BadUrl b , Nothing)

Http.Timeout_ ->
Err (Http.Timeout , Nothing)

Http.NetworkError_ ->
Err (Http.NetworkError , Nothing)

Http.BadStatus_ b c ->
Err (Http.BadStatus b.statusCode , Just { metadata = b, body = c })

Http.GoodStatus_ b c ->
Result.mapError (\d -> (Http.BadBody (Json.Decode.errorToString d) , Just { metadata = b
, body = c })) (Json.Decode.decodeString Api.User.decoder c))
, timeout = Nothing
, tracker = Nothing }

postUser : Api.User.User -> Cmd (Result (Http.Error , Maybe { metadata : Http.Metadata
, body : String }) ())
postUser a =
Http.request { method = "POST"
, headers = []
, url = Config.urlBase ++ "/user"
, body = Http.jsonBody (Api.User.encoder a)
, expect = Http.expectStringResponse identity (\b -> case b of
Http.BadUrl_ c ->
Err (Http.BadUrl c , Nothing)

Http.Timeout_ ->
Err (Http.Timeout , Nothing)

Http.NetworkError_ ->
Err (Http.NetworkError , Nothing)

Http.BadStatus_ c d ->
Err (Http.BadStatus c.statusCode , Just { metadata = c, body = d })

Http.GoodStatus_ c d ->
if d == "" then
Ok ()

else
Err (Http.BadBody "Expected the response body to be empty" , Just { metadata = c
, body = d }))
, timeout = Nothing
, tracker = Nothing }
module Api.User exposing (..)

import Json.Decode
import Json.Decode.Pipeline
import Json.Encode

type alias User =
{ name : String, age : Int }

encoder : User -> Json.Encode.Value
encoder a =
Json.Encode.object [ ("name" , Json.Encode.string a.name)
, ("age" , Json.Encode.int a.age) ]

decoder : Json.Decode.Decoder User
decoder =
Json.Decode.succeed User |>
Json.Decode.Pipeline.required "name" Json.Decode.string |>
Json.Decode.Pipeline.required "age" Json.Decode.int
```

In an actual project we would be writing the code to disk instead of printing it.

See [this file](examples/UserAPI.hs) for the full code with imports.

## Auth-protected routes

If you use `AuthProtect` from `Servant.API.Experimental.Auth`, the following
code can be used:

```haskell
instance HasElmEndpoints api => HasElmEndpoints (AuthProtect "auth" :> api) where
elmEndpoints' = elmEndpoints' @(Header' '[ Required, Strict] "Authorization" Token :> api)
```

This makes endpoints under `AuthProtect "auth"` take an extra `Token` parameter
which are added as authorization headers to the requests. This assumes that
`Token` has appropriate instances for `HasElmType` and `HasElmEncoder Text`.

## Related projects

Libraries that use or are used by servant-to-elm:
- [haskell-to-elm](https://github.com/folq/haskell-to-elm) generates Elm types and JSON encoders and decoders from Haskell types.
- [elm-syntax](https://github.com/folq/elm-syntax) defines Haskell ASTs for Elm's syntax, and lets us pretty-print it.
- [haskell-to-elm-test](https://github.com/folq/haskell-to-elm-test) does end-to-end testing of this library.

Others:
- [servant-elm](http://hackage.haskell.org/package/servant-elm)