/servant-elm

Automatically derive Elm functions to query servant webservices

Primary LanguageHaskellBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

Servant Elm

Build Status

Generate Elm functions to query your Servant API!

Elm type generation coutesy of krisajenkins/elm-export.

Installation

Until elm-export is released, servant-elm requires stack. Add this to your stack.yaml file:

...
resolver: lts-5.10

packages:
- '.'
- location:
    git: https://www.github.com/mattjbray/elm-export
    commit: 3dfafc7a717003ff4374119ff6f60e5b56868d8f
  extra-dep: True
- location:
    git: https://www.github.com/mattjbray/servant-elm
    commit: 36c90557d17d237e621cdcb4912ae9e4f25a9e59
  extra-dep: True

extra-deps:
- servant-0.5
- servant-foreign-0.5
- servant-server-0.5

Example

First, some language pragmas and imports.

{-# LANGUAGE DataKinds     #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}

import           GHC.Generics (Generic)
import           Servant.API  ((:>), Capture, Get, JSON)
import           Servant.Elm  (ElmType, Proxy (Proxy), Spec (Spec),
                               defElmImports, generateElmForAPI, specsToDir,
                               specsToDir)

We have some Haskell-defined types and our Servant API.

data Book = Book
  { name :: String
  } deriving (Generic)

instance ElmType Book

type BooksApi = "books" :> Capture "bookId" Int :> Get '[JSON] Book

Now we can generate Elm functions to query the API:

spec :: Spec
spec = Spec ["Generated", "MyApi"]
            (defElmImports
             : generateElmForAPI (Proxy :: Proxy BooksApi))

main :: IO ()
main = specsToDir [spec] "my-elm-dir"

Let's save this as example.hs and run it:

$ stack runghc example.hs
Writing: my-elm-dir/Generated/MyApi.elm
$

Here's what was generated:

module Generated.MyApi where

import Json.Decode exposing ((:=))
import Json.Decode.Extra exposing ((|:))
import Json.Encode
import Http
import String
import Task


type alias Book =
  { name : String
  }

decodeBook : Json.Decode.Decoder Book
decodeBook =
  Json.Decode.succeed Book
    |: ("name" := Json.Decode.string)

encodeBook : Book -> Json.Encode.Value
encodeBook x =
  Json.Encode.object
    [ ( "name", Json.Encode.string x.name )
    ]

getBooksByBookId : Int -> Task.Task Http.Error (Book)
getBooksByBookId bookId =
  let
    request =
      { verb =
          "GET"
      , headers =
          [("Content-Type", "application/json")]
      , url =
          "/" ++ "books"
          ++ "/" ++ (bookId |> toString |> Http.uriEncode)
      , body =
          Http.empty
      }
  in
    Http.fromJson
      decodeBook
      (Http.send Http.defaultSettings request)

See examples for a complete usage example, or take a look at mattjbray/servant-elm-example-app for an example project using this library.

Development

$ git clone https://github.com/mattjbray/servant-elm.git
$ cd servant-elm
$ stack test

To build all examples:

$ make examples

To run an example:

$ cd examples/e2e-tests
$ elm-reactor
# Open http://localhost:8000/elm/Main.elm

TODO

  • Encode captures and query params?
  • Option to not use elm-export: generate functions that take a decoder and String arguments.