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
|
||||
shell.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.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
source_suffix = ['.md', '.rst']
|
||||
source_suffix = ['.md', '.rst', '.lhs']
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
@ -47,8 +47,8 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'servant'
|
||||
copyright = u'2015-2016, Servant contributors'
|
||||
author = u'Servant contributors'
|
||||
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
|
||||
|
@ -74,7 +74,7 @@ language = None
|
|||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# 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
|
||||
# documents.
|
||||
|
@ -108,7 +108,7 @@ todo_include_todos = False
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# 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
|
||||
# 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]).
|
||||
latex_documents = [
|
||||
(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
|
||||
|
@ -285,4 +285,5 @@ texinfo_documents = [
|
|||
|
||||
source_parsers = {
|
||||
'.md': CommonMarkParser,
|
||||
'.lhs': CommonMarkParser,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ Documentation table of contents
|
|||
-------------------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
README.md
|
||||
tutorial/index.rst
|
||||
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::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
api-type.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