Help us understand the problem. What is going on with this article?

【Servant】(3) エンドポイントを増やす

【Servant】(1) Wai - Qiita
【Servant】(2) Servantチュートリアル - Qiita
【Servant】(3) エンドポイントを増やす - Qiita
【Servant】(4) URLパラメータをハンドラの引数とする - Qiita
【Servant】(5) JSON - Qiita
【Servant】(6) HTML - Qiita
【Servant】(7) Post Data - Qiita
【Servant】(8) Another Monad - Qiita
【Servant】(9) Handlerモナド - Qiita
【Servant】(10) SQLite - Qiita
【Servant】(11) Servant-Client - Qiita
【Servant】(12) Basic 認証 - Qiita

今回も公式ドキュメントに沿って話を進めます。前回作成したexampleのエンドポイントを増やしてみます。
Docs » Tutorial » Serving an API - A first example

1. エンドポイントを増やす

前回のソースです。

Lib.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveGeneric #-}

module Lib
    ( runServant
    ) where

-- import Servant(serve, Proxy(..), Server, JSON, Get, (:>))
import Servant
import Data.Aeson(ToJSON)
import Data.Time.Calendar
import GHC.Generics(Generic)
import Network.Wai(Application)
import Network.Wai.Handler.Warp(run)

data User = User
  { name :: String
  , age :: Int
  , email :: String
  , registration_date :: Day
  } deriving (Eq, Show, Generic)

instance ToJSON User


users1 :: [User]
users1 =
  [ User "Isaac Newton"    372 "isaac@newton.co.uk" (fromGregorian 1683  3 1)
  , User "Albert Einstein" 136 "ae@mc2.org"         (fromGregorian 1905 12 1)
  ]

type UserAPI1 = "users" :> Get '[JSON] [User]

server1 :: Server UserAPI1
server1 = return users1

userAPI :: Proxy UserAPI1
userAPI = Proxy

-- 'serve' comes from servant and hands you a WAI Application,
-- which you can think of as an "abstract" web application,
-- not yet a webserver.
app1 :: Application
app1 = serve userAPI server1

runServant :: IO ()
runServant = run 8081 app1

以下の部分を書き換えます。

Old
users1 :: [User]
users1 =
  [ User "Isaac Newton"    372 "isaac@newton.co.uk" (fromGregorian 1683  3 1)
  , User "Albert Einstein" 136 "ae@mc2.org"         (fromGregorian 1905 12 1)
  ]

type UserAPI1 = "users" :> Get '[JSON] [User]

server1 :: Server UserAPI1
server1 = return users1

APIのエンドポイントを3つに増やします。対応するハンドラも3つにします。3つのエンドポイントとハンドラは順番で対応付けされます。
見ての通り、複数のエンドポイントを連結するときも、複数のハンドラを連結するときも :<|>が使われています。型構成子と値構成子の違いを認識するのは重要です。

New
isaac :: User
isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)

albert :: User
albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)

users2 :: [User]
users2 = [isaac, albert]

type UserAPI2 = "users" :> Get '[JSON] [User]
           :<|> "albert" :> Get '[JSON] User
           :<|> "isaac" :> Get '[JSON] User

server2 :: Server UserAPI2
server2 = return users2
     :<|> return albert
     :<|> return isaac

置き換えた後の全ソースを掲載します。

Lib.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveGeneric #-}

module Lib
    ( runServant
    ) where

import Servant
import Data.Aeson(ToJSON)
import Data.Time.Calendar
import GHC.Generics(Generic)
import Network.Wai(Application)
import Network.Wai.Handler.Warp(run)

data User = User
  { name :: String
  , age :: Int
  , email :: String
  , registration_date :: Day
  } deriving (Eq, Show, Generic)

instance ToJSON User


isaac :: User
isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)

albert :: User
albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)

users2 :: [User]
users2 = [isaac, albert]

type UserAPI2 = "users" :> Get '[JSON] [User]
           :<|> "albert" :> Get '[JSON] User
           :<|> "isaac" :> Get '[JSON] User

server2 :: Server UserAPI2
server2 = return users2
     :<|> return albert
     :<|> return isaac

userAPI :: Proxy UserAPI2
userAPI = Proxy

-- 'serve' comes from servant and hands you a WAI Application,
-- which you can think of as an "abstract" web application,
-- not yet a webserver.
app2 :: Application
app2 = serve userAPI server2

runServant :: IO ()
runServant = run 8081 app2

動作を確認します。

パス"users"にアクセスすると2 Userが返ってきます。

$ curl http://localhost:8081/users
[{"email":"isaac@newton.co.uk","registration_date":"1683-03-01","age":372,"name":"Isaac Newton"},
{"email":"ae@mc2.org","registration_date":"1905-12-01","age":136,"name":"Albert Einstein"}]

パス"albert"にアクセスするとalbertが返ってきます。

$ curl http://localhost:8081/albert
{"email":"ae@mc2.org","registration_date":"1905-12-01","age":136,"name":"Albert Einstein"}

パス"isaac"にアクセスするとisaacが返ってきます。

$ curl http://localhost:8081/isaac
{"email":"isaac@newton.co.uk","registration_date":"1683-03-01","age":372,"name":"Isaac Newton"}

いい感じです。

2. serverの順番を変更する

それではもう少しServantの挙動を確認します。Serverの定義を少し変更して挙動を見てみます。

server2の1行目と3行目を入れ替えます。[User]とUserでは型が違うのでコンパイルエラーとなることを期待します。

type UserAPI2 = "users" :> Get '[JSON] [User]
           :<|> "albert" :> Get '[JSON] User
           :<|> "isaac" :> Get '[JSON] User

server2 :: Server UserAPI2
server2 = return isaac
     :<|> return albert
     :<|> return users2

実際コンパイルすると、コンパイルエラーで怒られます。エラーメッセージはあまりわかり易くはないですが。

次にserver2のisaacとalbertを入れ替えてみます。どちらもUser型なのでコンパイルが通ることが期待されます。

type UserAPI2 = "users" :> Get '[JSON] [User]
           :<|> "albert" :> Get '[JSON] User
           :<|> "isaac" :> Get '[JSON] User

server2 :: Server UserAPI2
server2 = return users2
     :<|> return isaac
     :<|> return albert

実際にコンパイルすると、成功します。ただしcurlでisaacパスを叩くとalbertが返ってきて、albertパスを叩くとisaacが返ってきます。これも期待通りです。

なんとなくServantの感じがつかめてきました。このシリーズは続きます。

sand
Haskell、Elm、Elixir、Phoenixなどが好きな言語です
http://www.mypress.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした