Moved tutorial to servant-examples/tutorial and include it in doc/index.rst
This commit is contained in:
parent
c908f549bb
commit
6afefdf611
28 changed files with 13 additions and 1053 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -25,4 +25,4 @@ Setup
|
||||||
.stack-work
|
.stack-work
|
||||||
shell.nix
|
shell.nix
|
||||||
default.nix
|
default.nix
|
||||||
tutorial/_build
|
doc/_build
|
||||||
|
|
13
doc/conf.py
13
doc/conf.py
|
@ -37,7 +37,7 @@ templates_path = ['_templates']
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
# The suffix(es) of source filenames.
|
||||||
# You can specify multiple suffix as a list of string:
|
# You can specify multiple suffix as a list of string:
|
||||||
source_suffix = ['.md', '.rst']
|
source_suffix = ['.md', '.rst', '.lhs']
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
#source_encoding = 'utf-8-sig'
|
#source_encoding = 'utf-8-sig'
|
||||||
|
@ -47,8 +47,8 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'servant'
|
project = u'servant'
|
||||||
copyright = u'2015-2016, Servant contributors'
|
copyright = u'2016, Servant Contributors'
|
||||||
author = u'Servant contributors'
|
author = u'Servant Contributors'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
@ -74,7 +74,7 @@ language = None
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ['_build', 'venv']
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
# documents.
|
# documents.
|
||||||
|
@ -108,7 +108,7 @@ todo_include_todos = False
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
# html_theme = 'alabaster'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
|
@ -223,7 +223,7 @@ latex_elements = {
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
(master_doc, 'servant.tex', u'servant Documentation',
|
(master_doc, 'servant.tex', u'servant Documentation',
|
||||||
u'Servant contributors', 'manual'),
|
u'Servant Contributors', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
@ -285,4 +285,5 @@ texinfo_documents = [
|
||||||
|
|
||||||
source_parsers = {
|
source_parsers = {
|
||||||
'.md': CommonMarkParser,
|
'.md': CommonMarkParser,
|
||||||
|
'.lhs': CommonMarkParser,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ Documentation table of contents
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
README.md
|
README.md
|
||||||
|
tutorial/index.rst
|
||||||
CONTRIBUTING.md
|
CONTRIBUTING.md
|
||||||
|
|
||||||
|
|
1
doc/tutorial
Symbolic link
1
doc/tutorial
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../servant-examples/tutorial
|
|
@ -1,45 +0,0 @@
|
||||||
{-# LANGUAGE CPP #-}
|
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T1 where
|
|
||||||
|
|
||||||
import Data.Aeson
|
|
||||||
import Data.Time.Calendar
|
|
||||||
import GHC.Generics
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
|
|
||||||
data User = User
|
|
||||||
{ name :: String
|
|
||||||
, age :: Int
|
|
||||||
, email :: String
|
|
||||||
, registration_date :: Day
|
|
||||||
} deriving (Eq, Show, Generic)
|
|
||||||
|
|
||||||
#if !MIN_VERSION_aeson(0,10,0)
|
|
||||||
-- orphan ToJSON instance for Day. necessary to derive one for User
|
|
||||||
instance ToJSON Day where
|
|
||||||
-- display a day in YYYY-mm-dd format
|
|
||||||
toJSON d = toJSON (showGregorian d)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
instance ToJSON User
|
|
||||||
|
|
||||||
type UserAPI = "users" :> Get '[JSON] [User]
|
|
||||||
|
|
||||||
users :: [User]
|
|
||||||
users =
|
|
||||||
[ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
|
|
||||||
, User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
userAPI :: Proxy UserAPI
|
|
||||||
userAPI = Proxy
|
|
||||||
|
|
||||||
server :: Server UserAPI
|
|
||||||
server = return users
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve userAPI EmptyConfig server
|
|
|
@ -1,71 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T10 where
|
|
||||||
|
|
||||||
import Data.ByteString.Lazy (ByteString)
|
|
||||||
import Data.Text.Lazy (pack)
|
|
||||||
import Data.Text.Lazy.Encoding (encodeUtf8)
|
|
||||||
import Network.HTTP.Types
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
import Servant.Docs
|
|
||||||
import qualified T3
|
|
||||||
|
|
||||||
type DocsAPI = T3.API :<|> Raw
|
|
||||||
|
|
||||||
instance ToCapture (Capture "x" Int) where
|
|
||||||
toCapture _ = DocCapture "x" "(integer) position on the x axis"
|
|
||||||
|
|
||||||
instance ToCapture (Capture "y" Int) where
|
|
||||||
toCapture _ = DocCapture "y" "(integer) position on the y axis"
|
|
||||||
|
|
||||||
instance ToSample T3.Position where
|
|
||||||
toSamples _ = singleSample (T3.Position 3 14)
|
|
||||||
|
|
||||||
instance ToParam (QueryParam "name" String) where
|
|
||||||
toParam _ =
|
|
||||||
DocQueryParam "name"
|
|
||||||
["Alp", "John Doe", "..."]
|
|
||||||
"Name of the person to say hello to."
|
|
||||||
Normal
|
|
||||||
|
|
||||||
instance ToSample T3.HelloMessage where
|
|
||||||
toSamples _ =
|
|
||||||
[ ("When a value is provided for 'name'", T3.HelloMessage "Hello, Alp")
|
|
||||||
, ("When 'name' is not specified", T3.HelloMessage "Hello, anonymous coward")
|
|
||||||
]
|
|
||||||
|
|
||||||
ci :: T3.ClientInfo
|
|
||||||
ci = T3.ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
|
|
||||||
|
|
||||||
instance ToSample T3.ClientInfo where
|
|
||||||
toSamples _ = singleSample ci
|
|
||||||
|
|
||||||
instance ToSample T3.Email where
|
|
||||||
toSamples _ = singleSample (T3.emailForClient ci)
|
|
||||||
|
|
||||||
api :: Proxy DocsAPI
|
|
||||||
api = Proxy
|
|
||||||
|
|
||||||
docsBS :: ByteString
|
|
||||||
docsBS = encodeUtf8
|
|
||||||
. pack
|
|
||||||
. markdown
|
|
||||||
$ docsWithIntros [intro] T3.api
|
|
||||||
|
|
||||||
where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]
|
|
||||||
|
|
||||||
server :: Server DocsAPI
|
|
||||||
server = T3.server :<|> serveDocs
|
|
||||||
|
|
||||||
where serveDocs _ respond =
|
|
||||||
respond $ responseLBS ok200 [plain] docsBS
|
|
||||||
|
|
||||||
plain = ("Content-Type", "text/plain")
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve api EmptyConfig server
|
|
|
@ -1,52 +0,0 @@
|
||||||
{-# LANGUAGE CPP #-}
|
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T2 where
|
|
||||||
|
|
||||||
import Data.Aeson
|
|
||||||
import Data.Time.Calendar
|
|
||||||
import GHC.Generics
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
|
|
||||||
data User = User
|
|
||||||
{ name :: String
|
|
||||||
, age :: Int
|
|
||||||
, email :: String
|
|
||||||
, registration_date :: Day
|
|
||||||
} deriving (Eq, Show, Generic)
|
|
||||||
|
|
||||||
#if !MIN_VERSION_aeson(0,10,0)
|
|
||||||
-- orphan ToJSON instance for Day. necessary to derive one for User
|
|
||||||
instance ToJSON Day where
|
|
||||||
-- display a day in YYYY-mm-dd format
|
|
||||||
toJSON d = toJSON (showGregorian d)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
instance ToJSON User
|
|
||||||
|
|
||||||
type UserAPI = "users" :> Get '[JSON] [User]
|
|
||||||
:<|> "albert" :> Get '[JSON] User
|
|
||||||
:<|> "isaac" :> Get '[JSON] 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)
|
|
||||||
|
|
||||||
users :: [User]
|
|
||||||
users = [isaac, albert]
|
|
||||||
|
|
||||||
userAPI :: Proxy UserAPI
|
|
||||||
userAPI = Proxy
|
|
||||||
|
|
||||||
server :: Server UserAPI
|
|
||||||
server = return users
|
|
||||||
:<|> return albert
|
|
||||||
:<|> return isaac
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve userAPI EmptyConfig server
|
|
|
@ -1,84 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T3 where
|
|
||||||
|
|
||||||
import Control.Monad.Trans.Except
|
|
||||||
import Data.Aeson
|
|
||||||
import Data.List
|
|
||||||
import GHC.Generics
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
|
|
||||||
data Position = Position
|
|
||||||
{ x :: Int
|
|
||||||
, y :: Int
|
|
||||||
} deriving (Show, Generic)
|
|
||||||
|
|
||||||
instance FromJSON Position
|
|
||||||
instance ToJSON Position
|
|
||||||
|
|
||||||
newtype HelloMessage = HelloMessage { msg :: String }
|
|
||||||
deriving (Show, Generic)
|
|
||||||
|
|
||||||
instance FromJSON HelloMessage
|
|
||||||
instance ToJSON HelloMessage
|
|
||||||
|
|
||||||
data ClientInfo = ClientInfo
|
|
||||||
{ name :: String
|
|
||||||
, email :: String
|
|
||||||
, age :: Int
|
|
||||||
, interested_in :: [String]
|
|
||||||
} deriving (Show, Generic)
|
|
||||||
|
|
||||||
instance FromJSON ClientInfo
|
|
||||||
instance ToJSON ClientInfo
|
|
||||||
|
|
||||||
data Email = Email
|
|
||||||
{ from :: String
|
|
||||||
, to :: String
|
|
||||||
, subject :: String
|
|
||||||
, body :: String
|
|
||||||
} deriving (Show, Generic)
|
|
||||||
|
|
||||||
instance FromJSON Email
|
|
||||||
instance ToJSON Email
|
|
||||||
|
|
||||||
emailForClient :: ClientInfo -> Email
|
|
||||||
emailForClient c = Email from' to' subject' body'
|
|
||||||
|
|
||||||
where from' = "great@company.com"
|
|
||||||
to' = email c
|
|
||||||
subject' = "Hey " ++ name c ++ ", we miss you!"
|
|
||||||
body' = "Hi " ++ name c ++ ",\n\n"
|
|
||||||
++ "Since you've recently turned " ++ show (age c)
|
|
||||||
++ ", have you checked out our latest "
|
|
||||||
++ intercalate ", " (interested_in c)
|
|
||||||
++ " products? Give us a visit!"
|
|
||||||
|
|
||||||
type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
|
|
||||||
:<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
|
|
||||||
:<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
|
|
||||||
|
|
||||||
api :: Proxy API
|
|
||||||
api = Proxy
|
|
||||||
|
|
||||||
server :: Server API
|
|
||||||
server = position
|
|
||||||
:<|> hello
|
|
||||||
:<|> marketing
|
|
||||||
|
|
||||||
where position :: Int -> Int -> ExceptT ServantErr IO Position
|
|
||||||
position x y = return (Position x y)
|
|
||||||
|
|
||||||
hello :: Maybe String -> ExceptT ServantErr IO HelloMessage
|
|
||||||
hello mname = return . HelloMessage $ case mname of
|
|
||||||
Nothing -> "Hello, anonymous coward"
|
|
||||||
Just n -> "Hello, " ++ n
|
|
||||||
|
|
||||||
marketing :: ClientInfo -> ExceptT ServantErr IO Email
|
|
||||||
marketing clientinfo = return (emailForClient clientinfo)
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve api EmptyConfig server
|
|
|
@ -1,63 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T4 where
|
|
||||||
|
|
||||||
import Data.Aeson
|
|
||||||
import Data.Foldable (foldMap)
|
|
||||||
import GHC.Generics
|
|
||||||
import Lucid
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
import Servant.HTML.Lucid
|
|
||||||
|
|
||||||
data Person = Person
|
|
||||||
{ firstName :: String
|
|
||||||
, lastName :: String
|
|
||||||
, age :: Int
|
|
||||||
} deriving Generic -- for the JSON instance
|
|
||||||
|
|
||||||
-- JSON serialization
|
|
||||||
instance ToJSON Person
|
|
||||||
|
|
||||||
-- HTML serialization of a single person
|
|
||||||
instance ToHtml Person where
|
|
||||||
toHtml person =
|
|
||||||
tr_ $ do
|
|
||||||
td_ (toHtml $ firstName person)
|
|
||||||
td_ (toHtml $ lastName person)
|
|
||||||
td_ (toHtml . show $ age person)
|
|
||||||
|
|
||||||
toHtmlRaw = toHtml
|
|
||||||
|
|
||||||
-- HTML serialization of a list of persons
|
|
||||||
instance ToHtml [Person] where
|
|
||||||
toHtml persons = table_ $ do
|
|
||||||
tr_ $ do
|
|
||||||
th_ "first name"
|
|
||||||
th_ "last name"
|
|
||||||
th_ "age"
|
|
||||||
|
|
||||||
foldMap toHtml persons
|
|
||||||
|
|
||||||
toHtmlRaw = toHtml
|
|
||||||
|
|
||||||
persons :: [Person]
|
|
||||||
persons =
|
|
||||||
[ Person "Isaac" "Newton" 372
|
|
||||||
, Person "Albert" "Einstein" 136
|
|
||||||
]
|
|
||||||
|
|
||||||
type PersonAPI = "persons" :> Get '[JSON, HTML] [Person]
|
|
||||||
|
|
||||||
personAPI :: Proxy PersonAPI
|
|
||||||
personAPI = Proxy
|
|
||||||
|
|
||||||
server :: Server PersonAPI
|
|
||||||
server = return persons
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve personAPI EmptyConfig server
|
|
|
@ -1,37 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T5 where
|
|
||||||
|
|
||||||
import Control.Monad.IO.Class
|
|
||||||
import Control.Monad.Trans.Except
|
|
||||||
import Data.Aeson
|
|
||||||
import GHC.Generics
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
import System.Directory
|
|
||||||
|
|
||||||
type IOAPI = "myfile.txt" :> Get '[JSON] FileContent
|
|
||||||
|
|
||||||
ioAPI :: Proxy IOAPI
|
|
||||||
ioAPI = Proxy
|
|
||||||
|
|
||||||
newtype FileContent = FileContent
|
|
||||||
{ content :: String }
|
|
||||||
deriving Generic
|
|
||||||
|
|
||||||
instance ToJSON FileContent
|
|
||||||
|
|
||||||
server :: Server IOAPI
|
|
||||||
server = do
|
|
||||||
exists <- liftIO (doesFileExist "myfile.txt")
|
|
||||||
if exists
|
|
||||||
then liftIO (readFile "myfile.txt") >>= return . FileContent
|
|
||||||
else throwE custom404Err
|
|
||||||
|
|
||||||
where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve ioAPI EmptyConfig server
|
|
|
@ -1,18 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T6 where
|
|
||||||
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
|
|
||||||
type API = "code" :> Raw
|
|
||||||
|
|
||||||
api :: Proxy API
|
|
||||||
api = Proxy
|
|
||||||
|
|
||||||
server :: Server API
|
|
||||||
server = serveDirectory "tutorial"
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve api EmptyConfig server
|
|
|
@ -1,33 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T7 where
|
|
||||||
|
|
||||||
import Control.Monad.Trans.Except
|
|
||||||
import Control.Monad.Trans.Reader
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
|
|
||||||
type ReaderAPI = "a" :> Get '[JSON] Int
|
|
||||||
:<|> "b" :> Get '[JSON] String
|
|
||||||
|
|
||||||
readerAPI :: Proxy ReaderAPI
|
|
||||||
readerAPI = Proxy
|
|
||||||
|
|
||||||
readerServerT :: ServerT ReaderAPI (Reader String)
|
|
||||||
readerServerT = a :<|> b
|
|
||||||
|
|
||||||
where a :: Reader String Int
|
|
||||||
a = return 1797
|
|
||||||
|
|
||||||
b :: Reader String String
|
|
||||||
b = ask
|
|
||||||
|
|
||||||
readerServer :: Server ReaderAPI
|
|
||||||
readerServer = enter readerToEither readerServerT
|
|
||||||
|
|
||||||
where readerToEither :: Reader String :~> ExceptT ServantErr IO
|
|
||||||
readerToEither = Nat $ \r -> return (runReader r "hi")
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve readerAPI EmptyConfig readerServer
|
|
|
@ -1,49 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T8 where
|
|
||||||
|
|
||||||
import Control.Monad.Trans.Except
|
|
||||||
import Network.HTTP.Client (Manager, defaultManagerSettings,
|
|
||||||
newManager)
|
|
||||||
import Servant
|
|
||||||
import Servant.Client
|
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
|
||||||
|
|
||||||
import T3
|
|
||||||
|
|
||||||
position :: Int -- ^ value for "x"
|
|
||||||
-> Int -- ^ value for "y"
|
|
||||||
-> ExceptT ServantError IO Position
|
|
||||||
|
|
||||||
hello :: Maybe String -- ^ an optional value for "name"
|
|
||||||
-> ExceptT ServantError IO HelloMessage
|
|
||||||
|
|
||||||
marketing :: ClientInfo -- ^ value for the request body
|
|
||||||
-> ExceptT ServantError IO Email
|
|
||||||
|
|
||||||
position :<|> hello :<|> marketing = client api baseUrl manager
|
|
||||||
|
|
||||||
baseUrl :: BaseUrl
|
|
||||||
baseUrl = BaseUrl Http "localhost" 8081 ""
|
|
||||||
|
|
||||||
{-# NOINLINE manager #-}
|
|
||||||
manager :: Manager
|
|
||||||
manager = unsafePerformIO $ newManager defaultManagerSettings
|
|
||||||
|
|
||||||
queries :: ExceptT ServantError IO (Position, HelloMessage, Email)
|
|
||||||
queries = do
|
|
||||||
pos <- position 10 10
|
|
||||||
msg <- hello (Just "servant")
|
|
||||||
em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
|
|
||||||
return (pos, msg, em)
|
|
||||||
|
|
||||||
run :: IO ()
|
|
||||||
run = do
|
|
||||||
res <- runExceptT queries
|
|
||||||
case res of
|
|
||||||
Left err -> putStrLn $ "Error: " ++ show err
|
|
||||||
Right (pos, msg, em) -> do
|
|
||||||
print pos
|
|
||||||
print msg
|
|
||||||
print em
|
|
|
@ -1,105 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
module T9 where
|
|
||||||
|
|
||||||
import Control.Applicative
|
|
||||||
import Control.Monad.IO.Class
|
|
||||||
import Data.Aeson
|
|
||||||
import Data.Text (Text)
|
|
||||||
import GHC.Generics
|
|
||||||
import Network.Wai
|
|
||||||
import Servant
|
|
||||||
import Servant.JS
|
|
||||||
import System.Random
|
|
||||||
|
|
||||||
import qualified Data.Text as T
|
|
||||||
import qualified Data.Text.IO as TIO
|
|
||||||
import qualified Language.Javascript.JQuery as JQ
|
|
||||||
|
|
||||||
data Point = Point
|
|
||||||
{ x :: Double
|
|
||||||
, y :: Double
|
|
||||||
} deriving Generic
|
|
||||||
|
|
||||||
instance ToJSON Point
|
|
||||||
|
|
||||||
randomPoint :: MonadIO m => m Point
|
|
||||||
randomPoint = liftIO . getStdRandom $ \g ->
|
|
||||||
let (rx, g') = randomR (-1, 1) g
|
|
||||||
(ry, g'') = randomR (-1, 1) g'
|
|
||||||
in (Point rx ry, g'')
|
|
||||||
|
|
||||||
data Search a = Search
|
|
||||||
{ query :: Text
|
|
||||||
, results :: [a]
|
|
||||||
} deriving Generic
|
|
||||||
|
|
||||||
mkSearch :: Text -> [a] -> Search a
|
|
||||||
mkSearch = Search
|
|
||||||
|
|
||||||
instance ToJSON a => ToJSON (Search a)
|
|
||||||
|
|
||||||
data Book = Book
|
|
||||||
{ author :: Text
|
|
||||||
, title :: Text
|
|
||||||
, year :: Int
|
|
||||||
} deriving Generic
|
|
||||||
|
|
||||||
instance ToJSON Book
|
|
||||||
|
|
||||||
book :: Text -> Text -> Int -> Book
|
|
||||||
book = Book
|
|
||||||
|
|
||||||
books :: [Book]
|
|
||||||
books =
|
|
||||||
[ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
|
|
||||||
, book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008
|
|
||||||
, book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
|
|
||||||
, book "Graham Hutton" "Programming in Haskell" 2007
|
|
||||||
, book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
|
|
||||||
, book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
|
|
||||||
]
|
|
||||||
|
|
||||||
searchBook :: Monad m => Maybe Text -> m (Search Book)
|
|
||||||
searchBook Nothing = return (mkSearch "" books)
|
|
||||||
searchBook (Just q) = return (mkSearch q books')
|
|
||||||
|
|
||||||
where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
|
|
||||||
|| q' `T.isInfixOf` T.toLower (title b)
|
|
||||||
)
|
|
||||||
books
|
|
||||||
q' = T.toLower q
|
|
||||||
|
|
||||||
type API = "point" :> Get '[JSON] Point
|
|
||||||
:<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
|
|
||||||
|
|
||||||
type API' = API :<|> Raw
|
|
||||||
|
|
||||||
api :: Proxy API
|
|
||||||
api = Proxy
|
|
||||||
|
|
||||||
api' :: Proxy API'
|
|
||||||
api' = Proxy
|
|
||||||
|
|
||||||
server :: Server API
|
|
||||||
server = randomPoint
|
|
||||||
:<|> searchBook
|
|
||||||
|
|
||||||
server' :: Server API'
|
|
||||||
server' = server
|
|
||||||
:<|> serveDirectory "tutorial/t9"
|
|
||||||
|
|
||||||
apiJS :: Text
|
|
||||||
apiJS = jsForAPI api jquery
|
|
||||||
|
|
||||||
writeJSFiles :: IO ()
|
|
||||||
writeJSFiles = do
|
|
||||||
TIO.writeFile "tutorial/t9/api.js" apiJS
|
|
||||||
jq <- TIO.readFile =<< JQ.file
|
|
||||||
TIO.writeFile "tutorial/t9/jq.js" jq
|
|
||||||
|
|
||||||
app :: Application
|
|
||||||
app = serve api' EmptyConfig server'
|
|
|
@ -59,7 +59,7 @@ Tutorial
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 1
|
||||||
|
|
||||||
api-type.lhs
|
api-type.lhs
|
||||||
server.lhs
|
server.lhs
|
|
@ -1,4 +0,0 @@
|
||||||
import T8
|
|
||||||
|
|
||||||
main :: IO ()
|
|
||||||
main = run
|
|
|
@ -1,26 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
<title>Tutorial - 9 - servant-jquery</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Books</h1>
|
|
||||||
<input type="search" name="q" id="q" placeholder="Search author or book title..." />
|
|
||||||
<div>
|
|
||||||
<p>Results for <strong id="query">""</strong></p>
|
|
||||||
<ul id="results">
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<h1>Approximating π</h1>
|
|
||||||
<p>Count: <span id="count">0</span></p>
|
|
||||||
<p>Successes: <span id="successes">0</span></p>
|
|
||||||
<p id="pi"></p>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="/jq.js"></script>
|
|
||||||
<script type="text/javascript" src="/api.js"></script>
|
|
||||||
<script type="text/javascript" src="/ui.js"></script>
|
|
||||||
|
|
||||||
</body>
|
|
|
@ -1,61 +0,0 @@
|
||||||
/* book search */
|
|
||||||
function updateResults(data)
|
|
||||||
{
|
|
||||||
console.log(data);
|
|
||||||
$('#results').html("");
|
|
||||||
$('#query').text("\"" + data.query + "\"");
|
|
||||||
for(var i = 0; i < data.results.length; i++)
|
|
||||||
{
|
|
||||||
$('#results').append(renderBook(data.results[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderBook(book)
|
|
||||||
{
|
|
||||||
var li = '<li><strong>' + book.title + '</strong>, <i>'
|
|
||||||
+ book.author + '</i> - ' + book.year + '</li>';
|
|
||||||
return li;
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchBooks()
|
|
||||||
{
|
|
||||||
var q = $('#q').val();
|
|
||||||
getBooks(q, updateResults, console.log)
|
|
||||||
}
|
|
||||||
|
|
||||||
searchBooks();
|
|
||||||
$('#q').keyup(function() {
|
|
||||||
searchBooks();
|
|
||||||
});
|
|
||||||
|
|
||||||
/* approximating pi */
|
|
||||||
var count = 0;
|
|
||||||
var successes = 0;
|
|
||||||
|
|
||||||
function f(data)
|
|
||||||
{
|
|
||||||
var x = data.x, y = data.y;
|
|
||||||
if(x*x + y*y <= 1)
|
|
||||||
{
|
|
||||||
successes++;
|
|
||||||
}
|
|
||||||
|
|
||||||
count++;
|
|
||||||
|
|
||||||
update('#count', count);
|
|
||||||
update('#successes', successes);
|
|
||||||
update('#pi', 4*successes/count);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(id, val)
|
|
||||||
{
|
|
||||||
$(id).text(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function refresh()
|
|
||||||
{
|
|
||||||
getPoint(f, console.log);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.setInterval(refresh, 200);
|
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import Network.Wai
|
|
||||||
import Network.Wai.Handler.Warp
|
|
||||||
import System.Environment
|
|
||||||
|
|
||||||
import qualified T1
|
|
||||||
import qualified T10
|
|
||||||
import qualified T2
|
|
||||||
import qualified T3
|
|
||||||
import qualified T4
|
|
||||||
import qualified T5
|
|
||||||
import qualified T6
|
|
||||||
import qualified T7
|
|
||||||
import qualified T9
|
|
||||||
|
|
||||||
app :: String -> (Application -> IO ()) -> IO ()
|
|
||||||
app n f = case n of
|
|
||||||
"1" -> f T1.app
|
|
||||||
"2" -> f T2.app
|
|
||||||
"3" -> f T3.app
|
|
||||||
"4" -> f T4.app
|
|
||||||
"5" -> f T5.app
|
|
||||||
"6" -> f T6.app
|
|
||||||
"7" -> f T7.app
|
|
||||||
"8" -> f T3.app
|
|
||||||
"9" -> T9.writeJSFiles >> f T9.app
|
|
||||||
"10" -> f T10.app
|
|
||||||
_ -> usage
|
|
||||||
|
|
||||||
main :: IO ()
|
|
||||||
main = do
|
|
||||||
args <- getArgs
|
|
||||||
case args of
|
|
||||||
[n] -> app n (run 8081)
|
|
||||||
_ -> usage
|
|
||||||
|
|
||||||
usage :: IO ()
|
|
||||||
usage = do
|
|
||||||
putStrLn "Usage:\t tutorial N"
|
|
||||||
putStrLn "\t\twhere N is the number of the example you want to run."
|
|
288
tutorial/conf.py
288
tutorial/conf.py
|
@ -1,288 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# servant documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Fri Jan 22 12:22:48 2016.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from recommonmark.parser import CommonMarkParser
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
#needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = []
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
|
||||||
# You can specify multiple suffix as a list of string:
|
|
||||||
source_suffix = ['.md', '.rst', '.lhs']
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'servant'
|
|
||||||
copyright = u'2016, Servant Contributors'
|
|
||||||
author = u'Servant Contributors'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
version = u'0.1'
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = u'0.1'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#
|
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
|
||||||
# Usually you set "language" from the command line for these cases.
|
|
||||||
language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
exclude_patterns = ['_build', 'venv']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
#modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
#keep_warnings = False
|
|
||||||
|
|
||||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
|
||||||
todo_include_todos = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'sphinx_rtd_theme'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
#html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
#html_title = None
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
#html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
|
||||||
# directly to the root of the documentation.
|
|
||||||
#html_extra_path = []
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = None
|
|
||||||
|
|
||||||
# Language to be used for generating the HTML full-text search index.
|
|
||||||
# Sphinx supports the following languages:
|
|
||||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
|
||||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
|
||||||
#html_search_language = 'en'
|
|
||||||
|
|
||||||
# A dictionary with options for the search language support, empty by default.
|
|
||||||
# Now only 'ja' uses this config value
|
|
||||||
#html_search_options = {'type': 'default'}
|
|
||||||
|
|
||||||
# The name of a javascript file (relative to the configuration directory) that
|
|
||||||
# implements a search results scorer. If empty, the default will be used.
|
|
||||||
#html_search_scorer = 'scorer.js'
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'servantdoc'
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
#'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#'preamble': '',
|
|
||||||
|
|
||||||
# Latex figure (float) alignment
|
|
||||||
#'figure_align': 'htbp',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title,
|
|
||||||
# author, documentclass [howto, manual, or own class]).
|
|
||||||
latex_documents = [
|
|
||||||
(master_doc, 'servant.tex', u'servant Documentation',
|
|
||||||
u'Servant Contributors', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
(master_doc, 'servant', u'servant Documentation',
|
|
||||||
[author], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
(master_doc, 'servant', u'servant Documentation',
|
|
||||||
author, 'servant', 'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
#texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
#texinfo_no_detailmenu = False
|
|
||||||
|
|
||||||
source_parsers = {
|
|
||||||
'.md': CommonMarkParser,
|
|
||||||
'.lhs': CommonMarkParser,
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
Servant tutorial
|
|
||||||
================
|
|
||||||
|
|
||||||
This is an introductory tutorial to the current version of *servant*, which is **0.4**. Any comment or issue can be directed to [this website's issue tracker](http://github.com/haskell-servant/haskell-servant.github.io/issues).
|
|
||||||
|
|
||||||
Github
|
|
||||||
-------
|
|
||||||
|
|
||||||
- the servant packages: [haskell-servant/servant](https://github.com/haskell-servant/servant)
|
|
||||||
- the website (including this tutorial): [haskell-servant/haskell-servant.github.io](https://github.com/haskell-servant/haskell-servant.github.io/)
|
|
||||||
- Feel free to use the issue tracker (or to send PRs!) on the website's repository to give feedback and suggestions about this tutorial
|
|
||||||
|
|
||||||
Introduction
|
|
||||||
-------------
|
|
||||||
|
|
||||||
*servant* has the following guiding principles:
|
|
||||||
|
|
||||||
- concision
|
|
||||||
|
|
||||||
This is a pretty wide-ranging principle. You should be able to get nice
|
|
||||||
documentation for your web servers, and client libraries, without repeating
|
|
||||||
yourself. You should not have to manually serialize and deserialize your
|
|
||||||
resources, but only declare how to do those things *once per type*. If a
|
|
||||||
bunch of your handlers take the same query parameters, you shouldn't have to
|
|
||||||
repeat that logic for each handler, but instead just "apply" it to all of
|
|
||||||
them at once. Your handlers shouldn't be where composition goes to die. And
|
|
||||||
so on.
|
|
||||||
|
|
||||||
- flexibility
|
|
||||||
|
|
||||||
If we haven't thought of your use case, it should still be easily
|
|
||||||
achievable. If you want to use templating library X, go ahead. Forms? Do
|
|
||||||
them however you want, but without difficulty. We're not opinionated.
|
|
||||||
|
|
||||||
- separation of concerns
|
|
||||||
|
|
||||||
Your handlers and your HTTP logic should be separate. True to the philosphy
|
|
||||||
at the core of HTTP and REST, with *servant* your handlers return normal
|
|
||||||
Haskell datatypes - that's the resource. And then from a description of your
|
|
||||||
API, *servant* handles the *presentation* (i.e., the Content-Types). But
|
|
||||||
that's just one example.
|
|
||||||
|
|
||||||
- type safety
|
|
||||||
|
|
||||||
Want to be sure your API meets a specification? Your compiler can check
|
|
||||||
that for you. Links you can be sure exist? You got it.
|
|
||||||
|
|
||||||
To stick true to these principles, we do things a little differently than you
|
|
||||||
might expect. The core idea is *reifying the description of your API*. Once
|
|
||||||
reified, everything follows. We think we might be the first web framework to
|
|
||||||
reify API descriptions in an extensible way. We're pretty sure we're the first
|
|
||||||
to reify it as *types*.
|
|
||||||
|
|
||||||
To be able to write a webservice you only need to read the first two sections,
|
|
||||||
but the goal of this document being to get you started with servant, we also
|
|
||||||
cover the couple of ways you can extend servant for a great good.
|
|
||||||
|
|
||||||
Tutorial
|
|
||||||
---------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
api-type.lhs
|
|
||||||
server.lhs
|
|
||||||
client.lhs
|
|
||||||
javascript.lhs
|
|
||||||
docs.lhs
|
|
Loading…
Reference in a new issue