【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. エンドポイントを増やす
前回のソースです。
{-# 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
以下の部分を書き換えます。
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つのエンドポイントとハンドラは順番で対応付けされます。
見ての通り、複数のエンドポイントを連結するときも、複数のハンドラを連結するときも **:<|>**が使われています。型構成子と値構成子の違いを認識するのは重要です。
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
置き換えた後の全ソースを掲載します。
{-# 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の感じがつかめてきました。このシリーズは続きます。