Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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.
- Host: GitHub
- URL: https://github.com/haskell-to-elm/servant-to-elm
- Owner: haskell-to-elm
- License: bsd-3-clause
- Created: 2019-08-23T11:13:35.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-05-26T10:24:04.000Z (over 1 year ago)
- Last Synced: 2024-10-28T14:19:52.968Z (2 months ago)
- Language: Haskell
- Size: 58.6 KB
- Stars: 29
- Watchers: 8
- Forks: 7
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
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 @Usermodules =
Pretty.modules $
Simplification.simplifyDefinition <$> definitionsforM_ (HashMap.toList modules) $ \(_moduleName, contents) ->
print contents
```Running `main` prints:
```elm
module Api exposing (..)import Api.User
import Config
import Http
import Json.DecodegetUser : 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.Encodetype 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)