LoginSignup
4
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-15

【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の感じがつかめてきました。このシリーズは続きます。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0