2015-02-19 02:48:10 +01:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
2014-12-20 21:58:07 +01:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
2015-12-25 02:13:42 +01:00
|
|
|
import Control.Lens ((&), (<>~))
|
2014-11-27 18:28:01 +01:00
|
|
|
import Data.Aeson
|
2014-12-10 16:43:43 +01:00
|
|
|
import Data.Proxy
|
2015-02-19 03:03:38 +01:00
|
|
|
import Data.String.Conversions
|
2015-02-19 02:48:10 +01:00
|
|
|
import Data.Text (Text)
|
2014-11-27 18:28:01 +01:00
|
|
|
import GHC.Generics
|
2014-12-10 16:43:43 +01:00
|
|
|
import Servant.API
|
2015-09-13 23:36:41 +02:00
|
|
|
import Servant.API.Authentication
|
2014-11-27 18:28:01 +01:00
|
|
|
import Servant.Docs
|
|
|
|
|
|
|
|
-- * Example
|
|
|
|
|
|
|
|
-- | A greet message data type
|
2015-01-30 05:45:00 +01:00
|
|
|
newtype Greet = Greet Text
|
2014-11-27 18:28:01 +01:00
|
|
|
deriving (Generic, Show)
|
|
|
|
|
2015-02-22 09:42:38 +01:00
|
|
|
-- | We can get JSON support automatically. This will be used to parse
|
|
|
|
-- and encode a Greeting as 'JSON'.
|
2014-11-27 18:28:01 +01:00
|
|
|
instance FromJSON Greet
|
|
|
|
instance ToJSON Greet
|
|
|
|
|
2015-02-22 09:42:38 +01:00
|
|
|
-- | We can also implement 'MimeRender' for additional formats like 'PlainText'.
|
|
|
|
instance MimeRender PlainText Greet where
|
2015-04-19 14:51:34 +02:00
|
|
|
mimeRender Proxy (Greet s) = "\"" <> cs s <> "\""
|
2015-02-19 02:48:10 +01:00
|
|
|
|
2015-09-13 23:36:41 +02:00
|
|
|
-- | Our required authentication object
|
|
|
|
newtype Cookie = Cookie String deriving (Show)
|
|
|
|
|
|
|
|
-- | Our user object returned when someone has been authenticated
|
|
|
|
newtype User = User String deriving (Show)
|
|
|
|
|
|
|
|
-- | Some secret data returned from the /private endpoint.
|
|
|
|
newtype SecretData = SecretData Text deriving (Generic, Show)
|
|
|
|
instance ToJSON SecretData
|
|
|
|
|
2014-11-27 18:28:01 +01:00
|
|
|
-- We add some useful annotations to our captures,
|
|
|
|
-- query parameters and request body to make the docs
|
|
|
|
-- really helpful.
|
|
|
|
instance ToCapture (Capture "name" Text) where
|
|
|
|
toCapture _ = DocCapture "name" "name of the person to greet"
|
|
|
|
|
|
|
|
instance ToCapture (Capture "greetid" Text) where
|
|
|
|
toCapture _ = DocCapture "greetid" "identifier of the greet msg to remove"
|
|
|
|
|
|
|
|
instance ToParam (QueryParam "capital" Bool) where
|
|
|
|
toParam _ =
|
|
|
|
DocQueryParam "capital"
|
|
|
|
["true", "false"]
|
2015-01-30 05:45:00 +01:00
|
|
|
"Get the greeting message in uppercase (true) or not (false).\
|
|
|
|
\Default is false."
|
2014-11-27 18:28:01 +01:00
|
|
|
Normal
|
|
|
|
|
2015-09-24 13:00:39 +02:00
|
|
|
instance ToSample Greet where
|
2015-05-02 03:21:03 +02:00
|
|
|
toSamples _ =
|
2015-01-04 16:53:02 +01:00
|
|
|
[ ("If you use ?capital=true", Greet "HELLO, HASKELLER")
|
|
|
|
, ("If you use ?capital=false", Greet "Hello, haskeller")
|
|
|
|
]
|
|
|
|
|
2015-09-24 13:00:39 +02:00
|
|
|
instance ToSample Int where
|
2015-09-19 01:27:51 +02:00
|
|
|
toSamples _ = singleSample 1729
|
2015-05-02 03:21:03 +02:00
|
|
|
|
2015-10-01 15:58:39 +02:00
|
|
|
instance ToSample User where
|
|
|
|
toSamples _ = singleSample (User "I'm a user!")
|
2015-09-13 23:36:41 +02:00
|
|
|
|
2015-10-01 15:58:39 +02:00
|
|
|
instance ToSample Cookie where
|
|
|
|
toSamples _ = singleSample (Cookie "cookie")
|
2015-09-13 23:36:41 +02:00
|
|
|
|
2015-10-01 15:58:39 +02:00
|
|
|
instance ToSample SecretData where
|
|
|
|
toSamples _ = singleSample (SecretData "shhhhh!")
|
2015-09-13 23:36:41 +02:00
|
|
|
|
2015-12-24 21:53:51 +01:00
|
|
|
instance ToAuthInfo (AuthProtect Cookie User mP mE uP uE) where
|
2015-09-13 23:36:41 +02:00
|
|
|
toAuthInfo _ = AuthenticationInfo "In this sentence we outline how authentication works."
|
|
|
|
"The following data is required on each request as a serialized header."
|
|
|
|
|
2015-02-18 02:49:08 +01:00
|
|
|
-- We define some introductory sections, these will appear at the top of the
|
|
|
|
-- documentation.
|
|
|
|
--
|
|
|
|
-- We pass them in with 'docsWith', below. If you only want to add
|
|
|
|
-- introductions, you may use 'docsWithIntros'
|
2015-01-30 05:45:00 +01:00
|
|
|
intro1 :: DocIntro
|
|
|
|
intro1 = DocIntro "On proper introductions." -- The title
|
2015-01-23 02:19:37 +01:00
|
|
|
[ "Hello there."
|
|
|
|
, "As documentation is usually written for humans, it's often useful \
|
|
|
|
\to introduce concepts with a few words." ] -- Elements are paragraphs
|
|
|
|
|
2015-01-30 05:45:00 +01:00
|
|
|
intro2 :: DocIntro
|
|
|
|
intro2 = DocIntro "This title is below the last"
|
2015-01-23 02:19:37 +01:00
|
|
|
[ "You'll also note that multiple intros are possible." ]
|
|
|
|
|
|
|
|
|
2014-11-27 18:28:01 +01:00
|
|
|
-- API specification
|
|
|
|
type TestApi =
|
2015-02-22 09:42:38 +01:00
|
|
|
-- GET /hello/:name?capital={true, false} returns a Greet as JSON or PlainText
|
2015-10-08 23:33:32 +02:00
|
|
|
"hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet
|
2014-11-27 18:28:01 +01:00
|
|
|
|
|
|
|
-- POST /greet with a Greet as JSON in the request body,
|
|
|
|
-- returns a Greet as JSON
|
2015-05-02 03:21:03 +02:00
|
|
|
:<|> "greet" :> ReqBody '[JSON] Greet :> Post '[JSON] (Headers '[Header "X-Example" Int] Greet)
|
2014-11-27 18:28:01 +01:00
|
|
|
|
|
|
|
-- DELETE /greet/:greetid
|
2015-05-06 21:21:35 +02:00
|
|
|
:<|> "greet" :> Capture "greetid" Text :> Delete '[JSON] ()
|
2014-11-27 18:28:01 +01:00
|
|
|
|
2015-09-13 23:36:41 +02:00
|
|
|
-- GET /private
|
2015-12-24 21:53:51 +01:00
|
|
|
:<|> "private" :> AuthProtect Cookie User 'Strict () 'Strict () :> Get '[JSON] SecretData
|
2015-09-13 23:41:35 +02:00
|
|
|
-- GET /private-lax
|
2015-12-24 21:53:51 +01:00
|
|
|
:<|> "private-lax" :> AuthProtect Cookie User 'Lax () 'Lax () :> Get '[JSON] SecretData
|
2015-09-13 23:36:41 +02:00
|
|
|
|
2015-01-30 05:45:00 +01:00
|
|
|
testApi :: Proxy TestApi
|
|
|
|
testApi = Proxy
|
2014-11-27 18:28:01 +01:00
|
|
|
|
2015-02-18 02:49:08 +01:00
|
|
|
-- Build some extra information for the DELETE /greet/:greetid endpoint. We
|
|
|
|
-- want to add documentation about a secret unicorn header and some extra
|
|
|
|
-- notes.
|
|
|
|
extra :: ExtraInfo TestApi
|
|
|
|
extra =
|
2015-05-06 21:21:35 +02:00
|
|
|
extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete '[JSON] ())) $
|
2015-02-07 05:17:39 +01:00
|
|
|
defAction & headers <>~ ["unicorns"]
|
|
|
|
& notes <>~ [ DocNote "Title" ["This is some text"]
|
|
|
|
, DocNote "Second secton" ["And some more"]
|
|
|
|
]
|
|
|
|
|
2014-11-27 18:28:01 +01:00
|
|
|
-- Generate the data that lets us have API docs. This
|
|
|
|
-- is derived from the type as well as from
|
|
|
|
-- the 'ToCapture', 'ToParam' and 'ToSample' instances from above.
|
2015-01-30 05:45:00 +01:00
|
|
|
--
|
2015-02-18 02:49:08 +01:00
|
|
|
-- If you didn't want intros and extra information, you could just call:
|
2015-01-30 05:45:00 +01:00
|
|
|
--
|
2015-02-18 02:49:08 +01:00
|
|
|
-- > docs testAPI :: API
|
2014-11-27 18:28:01 +01:00
|
|
|
docsGreet :: API
|
2015-09-21 12:36:57 +02:00
|
|
|
docsGreet = docsWith defaultDocOptions [intro1, intro2] extra testApi
|
2014-11-27 18:28:01 +01:00
|
|
|
|
|
|
|
main :: IO ()
|
2014-12-20 21:58:07 +01:00
|
|
|
main = putStrLn $ markdown docsGreet
|