servant-to-elm: Automatically generate Elm clients for Servant APIs

[ api, bsd3, compiler, elm, library, servant ] [ Propose Tags ]

Please see the README on GitHub at https://github.com/folq/servant-to-elm#readme


[Skip to Readme]

Modules

[Index] [Quick Jump]

Flags

Manual Flags

NameDescriptionDefault
examples

Build examples

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.2.0.0, 0.3.0.0, 0.3.1.0, 0.4.0.0, 0.4.1.0, 0.4.2.0, 0.4.3.0
Change log CHANGELOG.md
Dependencies aeson (>=1.4.0), base (>=4.7 && <5), bound (>=2.0.0), elm-syntax (>=0.3.0 && <0.3.1), generics-sop, haskell-to-elm (>=0.3.0 && <0.3.3), http-types (>=0.12.0), servant (>=0.17 && <0.19), servant-multipart (>=0.11.0), servant-to-elm, text (>=1.2.0), unordered-containers [details]
License BSD-3-Clause
Copyright 2019 Olle Fredriksson
Author Olle Fredriksson
Maintainer fredriksson.olle@gmail.com
Category Servant, API, Elm, Compiler
Home page https://github.com/folq/servant-to-elm#readme
Bug tracker https://github.com/folq/servant-to-elm/issues
Source repo head: git clone https://github.com/folq/servant-to-elm
Uploaded by seagreen at 2021-10-11T16:58:07Z
Distributions
Executables user-example
Downloads 2042 total (25 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
All reported builds failed as of 2021-10-11 [all 1 reports]

Readme for servant-to-elm-0.4.3.0

[back to package description]

servant-to-elm Hackage

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

See haskell-to-elm for background information and a more elaborate motivation.

Basic usage

Given a Servant API like the following

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:

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:

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 for the full code with imports.

Auth-protected routes

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

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.

Libraries that use or are used by servant-to-elm:

  • haskell-to-elm generates Elm types and JSON encoders and decoders from Haskell types.
  • elm-syntax defines Haskell ASTs for Elm's syntax, and lets us pretty-print it.
  • haskell-to-elm-test does end-to-end testing of this library.

Others: