on master help sign in
Merge pull request #31 from haskell-servant/merge

Merge all repos

commite9f73b09891b3badee45166400fac175691965f5 authorJulian Arni <jkarni@gmail.com> dateMon 20 Apr 2015, 14:23 (11 years ago) parent68a129d619e665
96 files changed+6024−16
.gitignore+6 .travis.yml+3−16 scripts/test-all.sh+42 servant-client/CHANGELOG.md+15 LICENSE → servant-client/LICENSE servant-client/README.md+23 Setup.hs → servant-client/Setup.hs servant-client/docs.sh+52 servant-client/servant-client.cabal+85 servant-client/src/Servant/Client.hs+504 servant-client/src/Servant/Common/BaseUrl.hs+55 servant-client/src/Servant/Common/Req.hs+184 servant-client/test/Servant/ClientSpec.hs+345 servant-client/test/Servant/Common/BaseUrlSpec.hs+69 test/Spec.hs → servant-client/test/Spec.hs servant-docs/CHANGELOG.md+15 servant-docs/LICENSE+30 servant-docs/README.md+72 servant-docs/Setup.hs+2 servant-docs/default.nix+19 servant-docs/docs.sh+52 servant-docs/example/greet.hs+117 servant-docs/example/greet.md+124 servant-docs/servant-docs.cabal+71 servant-docs/shell.nix+9 servant-docs/src/Servant/Docs.hs+168 servant-docs/src/Servant/Docs/Internal.hs+803 servant-docs/test/Servant/DocsSpec.hs+64 servant-docs/test/Spec.hs+1 servant-jquery/CHANGELOG.md+12 servant-jquery/LICENSE+30 servant-jquery/README.md+97 servant-jquery/Setup.hs+2 servant-jquery/TODO.md+1 servant-jquery/docs.sh+52 servant-jquery/examples/README.md+17 servant-jquery/examples/counter.hs+82 servant-jquery/examples/counter.md+39 servant-jquery/examples/www/api.js+20 servant-jquery/examples/www/index.html+40 servant-jquery/examples/www/jquery.min.js+4 servant-jquery/servant-jquery.cabal+78 servant-jquery/src/Servant/JQuery.hs+117 servant-jquery/src/Servant/JQuery/Internal.hs+339 servant-jquery/test/Servant/JQuerySpec.hs+96 servant-jquery/test/Servant/JQuerySpec/CustomHeaders.hs+55 servant-jquery/test/Spec.hs+2 servant-server/CHANGELOG.md+26 servant-server/LICENSE+30 servant-server/README.md+20 servant-server/Setup.hs+2 servant-server/default.nix+15 servant-server/example/README.md+2 servant-server/example/greet.hs+72 servant-server/example/greet.md+52 servant-server/servant-server.cabal+101 servant-server/src/Servant.hs+22 servant-server/src/Servant/Server.hs+49 servant-server/src/Servant/Server/Internal.hs+866 servant-server/src/Servant/Utils/StaticFiles.hs+36 servant-server/test/Servant/ServerSpec.hs+619 servant-server/test/Servant/Utils/StaticFilesSpec.hs+65 servant-server/test/Spec.hs+1 CHANGELOG.md → servant/CHANGELOG.md servant/LICENSE+30 README.md → servant/README.md servant/Setup.hs+2 default.nix → servant/default.nix docs.sh → servant/docs.sh servant.cabal → servant/servant.cabal servant.png → servant/servant.png src/Servant/API.hs → servant/src/Servant/API.hs src/Servant/API/Alternative.hs → servant/src/Servant/API/Alternative.hs src/Servant/API/Capture.hs → servant/src/Servant/API/Capture.hs src/Servant/API/ContentTypes.hs → servant/src/Servant/API/ContentTypes.hs src/Servant/API/Delete.hs → servant/src/Servant/API/Delete.hs src/Servant/API/Get.hs → servant/src/Servant/API/Get.hs src/Servant/API/Header.hs → servant/src/Servant/API/Header.hs src/Servant/API/MatrixParam.hs → servant/src/Servant/API/MatrixParam.hs src/Servant/API/Patch.hs → servant/src/Servant/API/Patch.hs src/Servant/API/Post.hs → servant/src/Servant/API/Post.hs src/Servant/API/Put.hs → servant/src/Servant/API/Put.hs src/Servant/API/QueryParam.hs → servant/src/Servant/API/QueryParam.hs src/Servant/API/Raw.hs → servant/src/Servant/API/Raw.hs src/Servant/API/ReqBody.hs → servant/src/Servant/API/ReqBody.hs src/Servant/API/ResponseHeaders.hs → servant/src/Servant/API/ResponseHeaders.hs src/Servant/API/Sub.hs → servant/src/Servant/API/Sub.hs src/Servant/Common/Text.hs → servant/src/Servant/Common/Text.hs src/Servant/Utils/Links.hs → servant/src/Servant/Utils/Links.hs test/Doctests.hs → servant/test/Doctests.hs test/Servant/API/ContentTypesSpec.hs → servant/test/Servant/API/ContentTypesSpec.hs test/Servant/API/ResponseHeadersSpec.hs → servant/test/Servant/API/ResponseHeadersSpec.hs test/Servant/Common/TextSpec.hs → servant/test/Servant/Common/TextSpec.hs test/Servant/Utils/LinksSpec.hs → servant/test/Servant/Utils/LinksSpec.hs servant/test/Spec.hs+1 update-nixpkgs.sh → servant/update-nixpkgs.sh
.gitignore+6
@@ -1,5 +1,11 @@
11dist
2+bin
3+lib
4+share
5+packages
6+*-packages.conf.d
27cabal-dev
8+add-source-timestamps
39*.o
410*.hi
511*.chi
.travis.yml+3−16
@@ -3,25 +3,12 @@ language: haskell
33ghc:
44 - 7.8
55
6−before_install:
7− - cabal update
8− - cabal sandbox init
9−
106install:
11− - cabal install --only-dependencies --enable-tests
7+ - ghc --version
8+ - cabal --version
129
1310script:
14− - cabal configure --enable-tests --enable-library-coverage && cabal build && cabal test
15− - cabal check
16− - cabal sdist
17−
18−after_script:
19− - |
20− if [ "$TRAVIS_PULL_REQUEST" -eq "$TRAVIS_PULL_REQUEST" ] 2>/dev/null || [ "$TRAVIS_BRANCH" == "master" ] ; then
21− cabal install hpc-coveralls
22− hpc-coveralls --exclude-dir=test spec doctests
23− fi
24−
11+ - ./scripts/test-all.sh
2512
2613notifications:
2714 irc:
scripts/test-all.shnew file+42
@@ -0,0 +1,42 @@
1+#!/bin/bash -
2+#===============================================================================
3+#
4+# FILE: test-all.sh
5+#
6+# USAGE: ./test-all.sh
7+#
8+# DESCRIPTION: Run tests for all source directories listed in $SOURCES.
9+# Uses local versions of those sources.
10+#
11+#===============================================================================
12+
13+set -o nounset
14+set -o errexit
15+
16+SOURCES=( servant servant-server servant-client servant-jquery servant-docs )
17+GHC_FLAGS="-Werror"
18+
19+prepare_sandbox () {
20+ cabal sandbox init
21+ for s in ${SOURCES[@]} ; do
22+ cd "$s"
23+ cabal sandbox init --sandbox=../
24+ cabal sandbox add-source .
25+ cd ..
26+ done
27+}
28+
29+test_each () {
30+ for s in ${SOURCES[@]} ; do
31+ echo "Testing $s..."
32+ cd "$s"
33+ cabal install --only-dependencies --enable-tests
34+ cabal configure --enable-tests --ghc-options="$GHC_FLAGS"
35+ cabal build
36+ cabal test
37+ cd ..
38+ done
39+}
40+
41+prepare_sandbox
42+test_each
servant-client/CHANGELOG.mdnew file+15
@@ -0,0 +1,15 @@
1+0.3
2+---
3+* Support content-type aware combinators and `Accept`/`Content-type` headers
4+* Added a lot of tests
5+* Support multiple concurrent threads
6+* Use `ServantError` to report Errors instead of `String`
7+* Make the clients for `Raw` endpoints return the whole `Response` value (to be able to access response headers for example)
8+* Support for PATCH
9+* Make () instances expect No Content status code, and not try to decode body.
10+* `Canonicalize` API types before generating client functions for them
11+
12+0.2.2
13+-----
14+* Add TLS support
15+* Add matrix parameter support
LICENSE → servant-client/LICENSErenamed

The content is the same.

servant-client/README.mdnew file+23
@@ -0,0 +1,23 @@
1+# servant-client
2+
3+[![Build Status](https://secure.travis-ci.org/haskell-servant/servant-client.svg)](http://travis-ci.org/haskell-servant/servant-client)
4+[![Coverage Status](https://coveralls.io/repos/haskell-servant/servant-client/badge.svg)](https://coveralls.io/r/haskell-servant/servant-client)
5+
6+![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
7+
8+This library lets you automatically derive Haskell functions that let you query each endpoint of a *servant* webservice.
9+
10+## Example
11+
12+``` haskell
13+type MyApi = "books" :> Get [Book] -- GET /books
14+ :<|> "books" :> ReqBody Book :> Post Book -- POST /books
15+
16+myApi :: Proxy MyApi
17+myApi = Proxy
18+
19+getAllBooks :: BaseUrl -> EitherT String IO [Book]
20+postNewBook :: Book -> BaseUrl -> EitherT String IO Book
21+-- 'client' allows you to produce operations to query an API from a client.
22+(getAllBooks :<|> postNewBook) = client myApi
23+```
Setup.hs → servant-client/Setup.hsrenamed

The content is the same.

servant-client/docs.shnew file+52
@@ -0,0 +1,52 @@
1+SERVANT_DIR=/tmp/servant-client-gh-pages
2+
3+# Make a temporary clone
4+
5+rm -rf $SERVANT_DIR
6+
7+git clone . $SERVANT_DIR
8+
9+cd $SERVANT_DIR
10+
11+# Make sure to pull the latest
12+
13+git remote add haskell-servant git@github.com:haskell-servant/servant-client.git
14+
15+git fetch haskell-servant
16+
17+git reset --hard haskell-servant/gh-pages
18+
19+# Clear everything away
20+
21+git rm -rf $SERVANT_DIR/*
22+
23+# Switch back and build the haddocks
24+
25+cd -
26+
27+cabal configure --builddir=$SERVANT_DIR
28+
29+cabal haddock --hoogle --hyperlink-source --html-location='https://hackage.haskell.org/package/$pkg-$version/docs' --builddir=$SERVANT_DIR
30+
31+commit_hash=$(git rev-parse HEAD)
32+
33+# Move the HTML docs to the root
34+
35+cd $SERVANT_DIR
36+
37+rm *
38+rm -rf build
39+mv doc/html/servant-client/* .
40+rm -r doc/
41+
42+# Add everything
43+
44+git add .
45+
46+git commit -m "Built from $commit_hash"
47+
48+# Push to update the pages
49+
50+git push haskell-servant HEAD:gh-pages
51+
52+rm -rf $SERVANT_DIR
servant-client/servant-client.cabalnew file+85
@@ -0,0 +1,85 @@
1+name: servant-client
2+version: 0.2.2
3+synopsis: automatical derivation of querying functions for servant webservices
4+description:
5+ This library lets you derive automatically Haskell functions that
6+ let you query each endpoint of a <http://hackage.haskell.org/package/servant servant> webservice.
7+ .
8+ Example below.
9+ .
10+ > type MyApi = "books" :> Get [Book] -- GET /books
11+ > :<|> "books" :> ReqBody Book :> Post Book -- POST /books
12+ >
13+ > myApi :: Proxy MyApi
14+ > myApi = Proxy
15+ >
16+ > getAllBooks :: BaseUrl -> EitherT String IO [Book]
17+ > postNewBook :: Book -> BaseUrl -> EitherT String IO Book
18+ > (getAllBooks :<|> postNewBook) = client myApi
19+license: BSD3
20+license-file: LICENSE
21+author: Alp Mestanogullari, Sönke Hahn, Julian K. Arni
22+maintainer: alpmestan@gmail.com
23+copyright: 2014 Zalora South East Asia Pte Ltd
24+category: Web
25+build-type: Simple
26+cabal-version: >=1.10
27+tested-with: GHC >= 7.8
28+homepage: http://haskell-servant.github.io/
29+Bug-reports: http://github.com/haskell-servant/servant-client/issues
30+source-repository head
31+ type: git
32+ location: http://github.com/haskell-servant/servant-client.git
33+
34+library
35+ exposed-modules:
36+ Servant.Client
37+ Servant.Common.BaseUrl
38+ Servant.Common.Req
39+ build-depends:
40+ base >=4.7 && <5
41+ , aeson
42+ , attoparsec
43+ , bytestring
44+ , either
45+ , exceptions
46+ , http-client
47+ , http-client-tls
48+ , http-media
49+ , http-types
50+ , network-uri >= 2.6
51+ , safe
52+ , servant >= 0.2.2
53+ , string-conversions
54+ , text
55+ , transformers
56+ hs-source-dirs: src
57+ default-language: Haskell2010
58+ ghc-options: -Wall
59+
60+test-suite spec
61+ type: exitcode-stdio-1.0
62+ ghc-options:
63+ -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures
64+ default-language: Haskell2010
65+ hs-source-dirs: test
66+ main-is: Spec.hs
67+ build-depends:
68+ base == 4.*
69+ , aeson
70+ , bytestring
71+ , deepseq
72+ , either
73+ , hspec == 2.*
74+ , http-client
75+ , http-media
76+ , http-types
77+ , HUnit
78+ , network >= 2.6
79+ , QuickCheck >= 2.7
80+ , servant >= 0.2.1
81+ , servant-client
82+ , servant-server >= 0.2.1
83+ , text
84+ , wai
85+ , warp
servant-client/src/Servant/Client.hsnew file+504
@@ -0,0 +1,504 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE InstanceSigs #-}
3+{-# LANGUAGE TypeFamilies #-}
4+{-# LANGUAGE TypeOperators #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE OverloadedStrings #-}
8+{-# LANGUAGE OverlappingInstances #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+-- | This module provides 'client' which can automatically generate
11+-- querying functions for each endpoint just from the type representing your
12+-- API.
13+module Servant.Client
14+ ( client
15+ , HasClient(..)
16+ , Client
17+ , ServantError(..)
18+ , module Servant.Common.BaseUrl
19+ ) where
20+
21+import Control.Monad
22+import Control.Monad.Trans.Either
23+import Data.ByteString.Lazy (ByteString)
24+import Data.List
25+import Data.Proxy
26+import Data.String.Conversions
27+import Data.Text (unpack)
28+import GHC.TypeLits
29+import Network.HTTP.Client (Response)
30+import Network.HTTP.Media
31+import qualified Network.HTTP.Types as H
32+import Servant.API
33+import Servant.API.ContentTypes
34+import Servant.Common.BaseUrl
35+import Servant.Common.Req
36+
37+-- * Accessing APIs as a Client
38+
39+-- | 'client' allows you to produce operations to query an API from a client.
40+--
41+-- > type MyApi = "books" :> Get [Book] -- GET /books
42+-- > :<|> "books" :> ReqBody Book :> Post Book -- POST /books
43+-- >
44+-- > myApi :: Proxy MyApi
45+-- > myApi = Proxy
46+-- >
47+-- > getAllBooks :: BaseUrl -> EitherT String IO [Book]
48+-- > postNewBook :: Book -> BaseUrl -> EitherT String IO Book
49+-- > (getAllBooks :<|> postNewBook) = client myApi
50+client :: HasClient (Canonicalize layout) => Proxy layout -> Client layout
51+client p = clientWithRoute (canonicalize p) defReq
52+
53+-- | This class lets us define how each API combinator
54+-- influences the creation of an HTTP request. It's mostly
55+-- an internal class, you can just use 'client'.
56+class HasClient layout where
57+ type Client' layout :: *
58+ clientWithRoute :: Proxy layout -> Req -> Client' layout
59+
60+type Client layout = Client' (Canonicalize layout)
61+
62+-- | A client querying function for @a ':<|>' b@ will actually hand you
63+-- one function for querying @a@ and another one for querying @b@,
64+-- stitching them together with ':<|>', which really is just like a pair.
65+--
66+-- > type MyApi = "books" :> Get [Book] -- GET /books
67+-- > :<|> "books" :> ReqBody Book :> Post Book -- POST /books
68+-- >
69+-- > myApi :: Proxy MyApi
70+-- > myApi = Proxy
71+-- >
72+-- > getAllBooks :: BaseUrl -> EitherT String IO [Book]
73+-- > postNewBook :: Book -> BaseUrl -> EitherT String IO Book
74+-- > (getAllBooks :<|> postNewBook) = client myApi
75+instance (HasClient a, HasClient b) => HasClient (a :<|> b) where
76+ type Client' (a :<|> b) = Client' a :<|> Client' b
77+ clientWithRoute Proxy req =
78+ clientWithRoute (Proxy :: Proxy a) req :<|>
79+ clientWithRoute (Proxy :: Proxy b) req
80+
81+-- | If you use a 'Capture' in one of your endpoints in your API,
82+-- the corresponding querying function will automatically take
83+-- an additional argument of the type specified by your 'Capture'.
84+-- That function will take care of inserting a textual representation
85+-- of this value at the right place in the request path.
86+--
87+-- You can control how values for this type are turned into
88+-- text by specifying a 'ToText' instance for your type.
89+--
90+-- Example:
91+--
92+-- > type MyApi = "books" :> Capture "isbn" Text :> Get Book
93+-- >
94+-- > myApi :: Proxy MyApi
95+-- > myApi = Proxy
96+-- >
97+-- > getBook :: Text -> BaseUrl -> EitherT String IO Book
98+-- > getBook = client myApi
99+-- > -- then you can just use "getBook" to query that endpoint
100+instance (KnownSymbol capture, ToText a, HasClient sublayout)
101+ => HasClient (Capture capture a :> sublayout) where
102+
103+ type Client' (Capture capture a :> sublayout) =
104+ a -> Client' sublayout
105+
106+ clientWithRoute Proxy req val =
107+ clientWithRoute (Proxy :: Proxy sublayout) $
108+ appendToPath p req
109+
110+ where p = unpack (toText val)
111+
112+-- | If you have a 'Delete' endpoint in your API, the client
113+-- side querying function that is created when calling 'client'
114+-- will just require an argument that specifies the scheme, host
115+-- and port to send the request to.
116+instance HasClient Delete where
117+ type Client' Delete = BaseUrl -> EitherT ServantError IO ()
118+
119+ clientWithRoute Proxy req host =
120+ void $ performRequest H.methodDelete req (`elem` [200, 202, 204]) host
121+
122+-- | If you have a 'Get' endpoint in your API, the client
123+-- side querying function that is created when calling 'client'
124+-- will just require an argument that specifies the scheme, host
125+-- and port to send the request to.
126+instance (MimeUnrender ct result) => HasClient (Get (ct ': cts) result) where
127+ type Client' (Get (ct ': cts) result) = BaseUrl -> EitherT ServantError IO result
128+ clientWithRoute Proxy req host =
129+ performRequestCT (Proxy :: Proxy ct) H.methodGet req [200, 203] host
130+
131+-- | If you have a 'Get xs ()' endpoint, the client expects a 204 No Content
132+-- HTTP header.
133+instance HasClient (Get (ct ': cts) ()) where
134+ type Client' (Get (ct ': cts) ()) = BaseUrl -> EitherT ServantError IO ()
135+ clientWithRoute Proxy req host =
136+ performRequestNoBody H.methodGet req [204] host
137+
138+-- | If you use a 'Header' in one of your endpoints in your API,
139+-- the corresponding querying function will automatically take
140+-- an additional argument of the type specified by your 'Header',
141+-- wrapped in Maybe.
142+--
143+-- That function will take care of encoding this argument as Text
144+-- in the request headers.
145+--
146+-- All you need is for your type to have a 'ToText' instance.
147+--
148+-- Example:
149+--
150+-- > newtype Referer = Referer Text
151+-- > deriving (Eq, Show, FromText, ToText)
152+-- >
153+-- > -- GET /view-my-referer
154+-- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get Referer
155+-- >
156+-- > myApi :: Proxy MyApi
157+-- > myApi = Proxy
158+-- >
159+-- > viewReferer :: Maybe Referer -> BaseUrl -> EitherT String IO Book
160+-- > viewReferer = client myApi
161+-- > -- then you can just use "viewRefer" to query that endpoint
162+-- > -- specifying Nothing or Just "http://haskell.org/" as arguments
163+instance (KnownSymbol sym, ToText a, HasClient sublayout)
164+ => HasClient (Header sym a :> sublayout) where
165+
166+ type Client' (Header sym a :> sublayout) =
167+ Maybe a -> Client' sublayout
168+
169+ clientWithRoute Proxy req mval =
170+ clientWithRoute (Proxy :: Proxy sublayout) $
171+ maybe req (\value -> Servant.Common.Req.addHeader hname value req) mval
172+
173+ where hname = symbolVal (Proxy :: Proxy sym)
174+
175+-- | If you have a 'Post' endpoint in your API, the client
176+-- side querying function that is created when calling 'client'
177+-- will just require an argument that specifies the scheme, host
178+-- and port to send the request to.
179+instance (MimeUnrender ct a) => HasClient (Post (ct ': cts) a) where
180+ type Client' (Post (ct ': cts) a) = BaseUrl -> EitherT ServantError IO a
181+
182+ clientWithRoute Proxy req uri =
183+ performRequestCT (Proxy :: Proxy ct) H.methodPost req [200,201] uri
184+
185+-- | If you have a 'Post xs ()' endpoint, the client expects a 204 No Content
186+-- HTTP header.
187+instance HasClient (Post (ct ': cts) ()) where
188+ type Client' (Post (ct ': cts) ()) = BaseUrl -> EitherT ServantError IO ()
189+ clientWithRoute Proxy req host =
190+ void $ performRequestNoBody H.methodPost req [204] host
191+
192+-- | If you have a 'Put' endpoint in your API, the client
193+-- side querying function that is created when calling 'client'
194+-- will just require an argument that specifies the scheme, host
195+-- and port to send the request to.
196+instance (MimeUnrender ct a) => HasClient (Put (ct ': cts) a) where
197+ type Client' (Put (ct ': cts) a) = BaseUrl -> EitherT ServantError IO a
198+
199+ clientWithRoute Proxy req host =
200+ performRequestCT (Proxy :: Proxy ct) H.methodPut req [200,201] host
201+
202+-- | If you have a 'Put xs ()' endpoint, the client expects a 204 No Content
203+-- HTTP header.
204+instance HasClient (Put (ct ': cts) ()) where
205+ type Client' (Put (ct ': cts) ()) = BaseUrl -> EitherT ServantError IO ()
206+ clientWithRoute Proxy req host =
207+ void $ performRequestNoBody H.methodPut req [204] host
208+
209+-- | If you have a 'Patch' endpoint in your API, the client
210+-- side querying function that is created when calling 'client'
211+-- will just require an argument that specifies the scheme, host
212+-- and port to send the request to.
213+instance (MimeUnrender ct a) => HasClient (Patch (ct ': cts) a) where
214+ type Client' (Patch (ct ': cts) a) = BaseUrl -> EitherT ServantError IO a
215+
216+ clientWithRoute Proxy req host =
217+ performRequestCT (Proxy :: Proxy ct) H.methodPatch req [200,201] host
218+
219+-- | If you have a 'Patch xs ()' endpoint, the client expects a 204 No Content
220+-- HTTP header.
221+instance HasClient (Patch (ct ': cts) ()) where
222+ type Client' (Patch (ct ': cts) ()) = BaseUrl -> EitherT ServantError IO ()
223+ clientWithRoute Proxy req host =
224+ void $ performRequestNoBody H.methodPatch req [204] host
225+
226+-- | If you use a 'QueryParam' in one of your endpoints in your API,
227+-- the corresponding querying function will automatically take
228+-- an additional argument of the type specified by your 'QueryParam',
229+-- enclosed in Maybe.
230+--
231+-- If you give Nothing, nothing will be added to the query string.
232+--
233+-- If you give a non-'Nothing' value, this function will take care
234+-- of inserting a textual representation of this value in the query string.
235+--
236+-- You can control how values for your type are turned into
237+-- text by specifying a 'ToText' instance for your type.
238+--
239+-- Example:
240+--
241+-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
242+-- >
243+-- > myApi :: Proxy MyApi
244+-- > myApi = Proxy
245+-- >
246+-- > getBooksBy :: Maybe Text -> BaseUrl -> EitherT String IO [Book]
247+-- > getBooksBy = client myApi
248+-- > -- then you can just use "getBooksBy" to query that endpoint.
249+-- > -- 'getBooksBy Nothing' for all books
250+-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
251+instance (KnownSymbol sym, ToText a, HasClient sublayout)
252+ => HasClient (QueryParam sym a :> sublayout) where
253+
254+ type Client' (QueryParam sym a :> sublayout) =
255+ Maybe a -> Client' sublayout
256+
257+ -- if mparam = Nothing, we don't add it to the query string
258+ clientWithRoute Proxy req mparam =
259+ clientWithRoute (Proxy :: Proxy sublayout) $
260+ maybe req (flip (appendToQueryString pname) req . Just) mparamText
261+
262+ where pname = cs pname'
263+ pname' = symbolVal (Proxy :: Proxy sym)
264+ mparamText = fmap toText mparam
265+
266+-- | If you use a 'QueryParams' in one of your endpoints in your API,
267+-- the corresponding querying function will automatically take
268+-- an additional argument, a list of values of the type specified
269+-- by your 'QueryParams'.
270+--
271+-- If you give an empty list, nothing will be added to the query string.
272+--
273+-- Otherwise, this function will take care
274+-- of inserting a textual representation of your values in the query string,
275+-- under the same query string parameter name.
276+--
277+-- You can control how values for your type are turned into
278+-- text by specifying a 'ToText' instance for your type.
279+--
280+-- Example:
281+--
282+-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
283+-- >
284+-- > myApi :: Proxy MyApi
285+-- > myApi = Proxy
286+-- >
287+-- > getBooksBy :: [Text] -> BaseUrl -> EitherT String IO [Book]
288+-- > getBooksBy = client myApi
289+-- > -- then you can just use "getBooksBy" to query that endpoint.
290+-- > -- 'getBooksBy []' for all books
291+-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
292+-- > -- to get all books by Asimov and Heinlein
293+instance (KnownSymbol sym, ToText a, HasClient sublayout)
294+ => HasClient (QueryParams sym a :> sublayout) where
295+
296+ type Client' (QueryParams sym a :> sublayout) =
297+ [a] -> Client' sublayout
298+
299+ clientWithRoute Proxy req paramlist =
300+ clientWithRoute (Proxy :: Proxy sublayout) $
301+ foldl' (\ req' -> maybe req' (flip (appendToQueryString pname) req' . Just)) req paramlist'
302+
303+ where pname = cs pname'
304+ pname' = symbolVal (Proxy :: Proxy sym)
305+ paramlist' = map (Just . toText) paramlist
306+
307+-- | If you use a 'QueryFlag' in one of your endpoints in your API,
308+-- the corresponding querying function will automatically take
309+-- an additional 'Bool' argument.
310+--
311+-- If you give 'False', nothing will be added to the query string.
312+--
313+-- Otherwise, this function will insert a value-less query string
314+-- parameter under the name associated to your 'QueryFlag'.
315+--
316+-- Example:
317+--
318+-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book]
319+-- >
320+-- > myApi :: Proxy MyApi
321+-- > myApi = Proxy
322+-- >
323+-- > getBooks :: Bool -> BaseUrl -> EitherT String IO [Book]
324+-- > getBooks = client myApi
325+-- > -- then you can just use "getBooks" to query that endpoint.
326+-- > -- 'getBooksBy False' for all books
327+-- > -- 'getBooksBy True' to only get _already published_ books
328+instance (KnownSymbol sym, HasClient sublayout)
329+ => HasClient (QueryFlag sym :> sublayout) where
330+
331+ type Client' (QueryFlag sym :> sublayout) =
332+ Bool -> Client' sublayout
333+
334+ clientWithRoute Proxy req flag =
335+ clientWithRoute (Proxy :: Proxy sublayout) $
336+ if flag
337+ then appendToQueryString paramname Nothing req
338+ else req
339+
340+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
341+
342+-- | If you use a 'MatrixParam' in one of your endpoints in your API,
343+-- the corresponding querying function will automatically take
344+-- an additional argument of the type specified by your 'MatrixParam',
345+-- enclosed in Maybe.
346+--
347+-- If you give Nothing, nothing will be added to the query string.
348+--
349+-- If you give a non-'Nothing' value, this function will take care
350+-- of inserting a textual representation of this value in the query string.
351+--
352+-- You can control how values for your type are turned into
353+-- text by specifying a 'ToText' instance for your type.
354+--
355+-- Example:
356+--
357+-- > type MyApi = "books" :> MatrixParam "author" Text :> Get [Book]
358+-- >
359+-- > myApi :: Proxy MyApi
360+-- > myApi = Proxy
361+-- >
362+-- > getBooksBy :: Maybe Text -> BaseUrl -> EitherT String IO [Book]
363+-- > getBooksBy = client myApi
364+-- > -- then you can just use "getBooksBy" to query that endpoint.
365+-- > -- 'getBooksBy Nothing' for all books
366+-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
367+instance (KnownSymbol sym, ToText a, HasClient sublayout)
368+ => HasClient (MatrixParam sym a :> sublayout) where
369+
370+ type Client' (MatrixParam sym a :> sublayout) =
371+ Maybe a -> Client' sublayout
372+
373+ -- if mparam = Nothing, we don't add it to the query string
374+ clientWithRoute Proxy req mparam =
375+ clientWithRoute (Proxy :: Proxy sublayout) $
376+ maybe req (flip (appendToMatrixParams pname . Just) req) mparamText
377+
378+ where pname = symbolVal (Proxy :: Proxy sym)
379+ mparamText = fmap (cs . toText) mparam
380+
381+-- | If you use a 'MatrixParams' in one of your endpoints in your API,
382+-- the corresponding querying function will automatically take an
383+-- additional argument, a list of values of the type specified by your
384+-- 'MatrixParams'.
385+--
386+-- If you give an empty list, nothing will be added to the query string.
387+--
388+-- Otherwise, this function will take care of inserting a textual
389+-- representation of your values in the path segment string, under the
390+-- same matrix string parameter name.
391+--
392+-- You can control how values for your type are turned into text by
393+-- specifying a 'ToText' instance for your type.
394+--
395+-- Example:
396+--
397+-- > type MyApi = "books" :> MatrixParams "authors" Text :> Get [Book]
398+-- >
399+-- > myApi :: Proxy MyApi
400+-- > myApi = Proxy
401+-- >
402+-- > getBooksBy :: [Text] -> BaseUrl -> EitherT String IO [Book]
403+-- > getBooksBy = client myApi
404+-- > -- then you can just use "getBooksBy" to query that endpoint.
405+-- > -- 'getBooksBy []' for all books
406+-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
407+-- > -- to get all books by Asimov and Heinlein
408+instance (KnownSymbol sym, ToText a, HasClient sublayout)
409+ => HasClient (MatrixParams sym a :> sublayout) where
410+
411+ type Client' (MatrixParams sym a :> sublayout) =
412+ [a] -> Client' sublayout
413+
414+ clientWithRoute Proxy req paramlist =
415+ clientWithRoute (Proxy :: Proxy sublayout) $
416+ foldl' (\ req' value -> maybe req' (flip (appendToMatrixParams pname) req' . Just . cs) value) req paramlist'
417+
418+ where pname = cs pname'
419+ pname' = symbolVal (Proxy :: Proxy sym)
420+ paramlist' = map (Just . toText) paramlist
421+
422+-- | If you use a 'MatrixFlag' in one of your endpoints in your API,
423+-- the corresponding querying function will automatically take an
424+-- additional 'Bool' argument.
425+--
426+-- If you give 'False', nothing will be added to the path segment.
427+--
428+-- Otherwise, this function will insert a value-less matrix parameter
429+-- under the name associated to your 'MatrixFlag'.
430+--
431+-- Example:
432+--
433+-- > type MyApi = "books" :> MatrixFlag "published" :> Get [Book]
434+-- >
435+-- > myApi :: Proxy MyApi
436+-- > myApi = Proxy
437+-- >
438+-- > getBooks :: Bool -> BaseUrl -> EitherT String IO [Book]
439+-- > getBooks = client myApi
440+-- > -- then you can just use "getBooks" to query that endpoint.
441+-- > -- 'getBooksBy False' for all books
442+-- > -- 'getBooksBy True' to only get _already published_ books
443+instance (KnownSymbol sym, HasClient sublayout)
444+ => HasClient (MatrixFlag sym :> sublayout) where
445+
446+ type Client' (MatrixFlag sym :> sublayout) =
447+ Bool -> Client' sublayout
448+
449+ clientWithRoute Proxy req flag =
450+ clientWithRoute (Proxy :: Proxy sublayout) $
451+ if flag
452+ then appendToMatrixParams paramname Nothing req
453+ else req
454+
455+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
456+
457+-- | Pick a 'Method' and specify where the server you want to query is. You get
458+-- back the full `Response`.
459+instance HasClient Raw where
460+ type Client' Raw = H.Method -> BaseUrl -> EitherT ServantError IO (Int, ByteString, MediaType, Response ByteString)
461+
462+ clientWithRoute :: Proxy Raw -> Req -> Client' Raw
463+ clientWithRoute Proxy req httpMethod host = do
464+ performRequest httpMethod req (const True) host
465+
466+-- | If you use a 'ReqBody' in one of your endpoints in your API,
467+-- the corresponding querying function will automatically take
468+-- an additional argument of the type specified by your 'ReqBody'.
469+-- That function will take care of encoding this argument as JSON and
470+-- of using it as the request body.
471+--
472+-- All you need is for your type to have a 'ToJSON' instance.
473+--
474+-- Example:
475+--
476+-- > type MyApi = "books" :> ReqBody Book :> Post Book
477+-- >
478+-- > myApi :: Proxy MyApi
479+-- > myApi = Proxy
480+-- >
481+-- > addBook :: Book -> BaseUrl -> EitherT String IO Book
482+-- > addBook = client myApi
483+-- > -- then you can just use "addBook" to query that endpoint
484+instance (MimeRender ct a, HasClient sublayout)
485+ => HasClient (ReqBody (ct ': cts) a :> sublayout) where
486+
487+ type Client' (ReqBody (ct ': cts) a :> sublayout) =
488+ a -> Client' sublayout
489+
490+ clientWithRoute Proxy req body =
491+ clientWithRoute (Proxy :: Proxy sublayout) $ do
492+ let ctProxy = Proxy :: Proxy ct
493+ setRQBody (mimeRender ctProxy body) (contentType ctProxy) req
494+
495+-- | Make the querying function append @path@ to the request path.
496+instance (KnownSymbol path, HasClient sublayout) => HasClient (path :> sublayout) where
497+ type Client' (path :> sublayout) = Client' sublayout
498+
499+ clientWithRoute Proxy req =
500+ clientWithRoute (Proxy :: Proxy sublayout) $
501+ appendToPath p req
502+
503+ where p = symbolVal (Proxy :: Proxy path)
504+
servant-client/src/Servant/Common/BaseUrl.hsnew file+55
@@ -0,0 +1,55 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE ViewPatterns #-}
3+module Servant.Common.BaseUrl where
4+
5+import Data.List
6+import GHC.Generics
7+import Network.URI
8+import Safe
9+import Text.Read
10+
11+-- | URI scheme to use
12+data Scheme =
13+ Http -- ^ http://
14+ | Https -- ^ https://
15+ deriving (Show, Eq, Ord, Generic)
16+
17+-- | Simple data type to represent the target of HTTP requests
18+-- for servant's automatically-generated clients.
19+data BaseUrl = BaseUrl
20+ { baseUrlScheme :: Scheme -- ^ URI scheme to use
21+ , baseUrlHost :: String -- ^ host (eg "haskell.org")
22+ , baseUrlPort :: Int -- ^ port (eg 80)
23+ } deriving (Show, Eq, Ord, Generic)
24+
25+showBaseUrl :: BaseUrl -> String
26+showBaseUrl (BaseUrl urlscheme host port) =
27+ schemeString ++ "//" ++ host ++ portString
28+ where
29+ schemeString = case urlscheme of
30+ Http -> "http:"
31+ Https -> "https:"
32+ portString = case (urlscheme, port) of
33+ (Http, 80) -> ""
34+ (Https, 443) -> ""
35+ _ -> ":" ++ show port
36+
37+parseBaseUrl :: String -> Either String BaseUrl
38+parseBaseUrl s = case parseURI (removeTrailingSlash s) of
39+ -- This is a rather hacky implementation and should be replaced with something
40+ -- implemented in attoparsec (which is already a dependency anyhow (via aeson)).
41+ Just (URI "http:" (Just (URIAuth "" host (':' : (readMaybe -> Just port)))) "" "" "") ->
42+ Right (BaseUrl Http host port)
43+ Just (URI "http:" (Just (URIAuth "" host "")) "" "" "") ->
44+ Right (BaseUrl Http host 80)
45+ Just (URI "https:" (Just (URIAuth "" host (':' : (readMaybe -> Just port)))) "" "" "") ->
46+ Right (BaseUrl Https host port)
47+ Just (URI "https:" (Just (URIAuth "" host "")) "" "" "") ->
48+ Right (BaseUrl Https host 443)
49+ _ -> if "://" `isInfixOf` s
50+ then Left ("invalid base url: " ++ s)
51+ else parseBaseUrl ("http://" ++ s)
52+ where
53+ removeTrailingSlash str = case lastMay str of
54+ Just '/' -> init str
55+ _ -> str
servant-client/src/Servant/Common/Req.hsnew file+184
@@ -0,0 +1,184 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+module Servant.Common.Req where
4+
5+import Control.Applicative
6+import Control.Exception
7+import Control.Monad
8+import Control.Monad.Catch (MonadThrow)
9+import Control.Monad.IO.Class
10+import Control.Monad.Trans.Either
11+import Data.ByteString.Lazy hiding (pack, filter, map, null, elem)
12+import Data.IORef
13+import Data.String
14+import Data.String.Conversions
15+import Data.Proxy
16+import Data.Text (Text)
17+import Data.Text.Encoding
18+import Network.HTTP.Client hiding (Proxy)
19+import Network.HTTP.Client.TLS
20+import Network.HTTP.Media
21+import Network.HTTP.Types
22+import Network.URI
23+import Servant.API.ContentTypes
24+import Servant.Common.BaseUrl
25+import Servant.Common.Text
26+import System.IO.Unsafe
27+
28+import qualified Network.HTTP.Client as Client
29+
30+data ServantError
31+ = FailureResponse
32+ { responseStatus :: Status
33+ , responseContentType :: MediaType
34+ , responseBody :: ByteString
35+ }
36+ | DecodeFailure
37+ { decodeError :: String
38+ , responseContentType :: MediaType
39+ , responseBody :: ByteString
40+ }
41+ | UnsupportedContentType
42+ { responseContentType :: MediaType
43+ , responseBody :: ByteString
44+ }
45+ | ConnectionError
46+ { connectionError :: HttpException
47+ }
48+ | InvalidContentTypeHeader
49+ { responseContentTypeHeader :: ByteString
50+ , responseBody :: ByteString
51+ }
52+ deriving (Show)
53+
54+data Req = Req
55+ { reqPath :: String
56+ , qs :: QueryText
57+ , reqBody :: Maybe (ByteString, MediaType)
58+ , reqAccept :: [MediaType]
59+ , headers :: [(String, Text)]
60+ }
61+
62+defReq :: Req
63+defReq = Req "" [] Nothing [] []
64+
65+appendToPath :: String -> Req -> Req
66+appendToPath p req =
67+ req { reqPath = reqPath req ++ "/" ++ p }
68+
69+appendToMatrixParams :: String
70+ -> Maybe String
71+ -> Req
72+ -> Req
73+appendToMatrixParams pname pvalue req =
74+ req { reqPath = reqPath req ++ ";" ++ pname ++ maybe "" ("=" ++) pvalue }
75+
76+appendToQueryString :: Text -- ^ param name
77+ -> Maybe Text -- ^ param value
78+ -> Req
79+ -> Req
80+appendToQueryString pname pvalue req =
81+ req { qs = qs req ++ [(pname, pvalue)]
82+ }
83+
84+addHeader :: ToText a => String -> a -> Req -> Req
85+addHeader name val req = req { headers = headers req
86+ ++ [(name, toText val)]
87+ }
88+
89+setRQBody :: ByteString -> MediaType -> Req -> Req
90+setRQBody b t req = req { reqBody = Just (b, t) }
91+
92+reqToRequest :: (Functor m, MonadThrow m) => Req -> BaseUrl -> m Request
93+reqToRequest req (BaseUrl reqScheme reqHost reqPort) =
94+ fmap (setheaders . setAccept . setrqb . setQS ) $ parseUrl url
95+
96+ where url = show $ nullURI { uriScheme = case reqScheme of
97+ Http -> "http:"
98+ Https -> "https:"
99+ , uriAuthority = Just $
100+ URIAuth { uriUserInfo = ""
101+ , uriRegName = reqHost
102+ , uriPort = ":" ++ show reqPort
103+ }
104+ , uriPath = reqPath req
105+ }
106+
107+ setrqb r = case reqBody req of
108+ Nothing -> r
109+ Just (b,t) -> r { requestBody = RequestBodyLBS b
110+ , requestHeaders = requestHeaders r
111+ ++ [(hContentType, cs . show $ t)] }
112+ setQS = setQueryString $ queryTextToQuery (qs req)
113+ setheaders r = r { requestHeaders = requestHeaders r
114+ <> fmap toProperHeader (headers req) }
115+ setAccept r = r { requestHeaders = filter ((/= "Accept") . fst) (requestHeaders r)
116+ <> [("Accept", renderHeader $ reqAccept req)
117+ | not . null . reqAccept $ req] }
118+ toProperHeader (name, val) =
119+ (fromString name, encodeUtf8 val)
120+
121+
122+-- * performing requests
123+
124+{-# NOINLINE __manager #-}
125+__manager :: IORef Manager
126+__manager = unsafePerformIO (newManager tlsManagerSettings >>= newIORef)
127+
128+__withGlobalManager :: (Manager -> IO a) -> IO a
129+__withGlobalManager action = readIORef __manager >>= action
130+
131+
132+displayHttpRequest :: Method -> String
133+displayHttpRequest httpmethod = "HTTP " ++ cs httpmethod ++ " request"
134+
135+
136+performRequest :: Method -> Req -> (Int -> Bool) -> BaseUrl -> EitherT ServantError IO (Int, ByteString, MediaType, Response ByteString)
137+performRequest reqMethod req isWantedStatus reqHost = do
138+ partialRequest <- liftIO $ reqToRequest req reqHost
139+
140+ let request = partialRequest { Client.method = reqMethod
141+ , checkStatus = \ _status _headers _cookies -> Nothing
142+ }
143+
144+ eResponse <- liftIO $ __withGlobalManager $ \ manager ->
145+ catchHttpException $
146+ Client.httpLbs request manager
147+ case eResponse of
148+ Left err ->
149+ left $ ConnectionError err
150+
151+ Right response -> do
152+ let status = Client.responseStatus response
153+ body = Client.responseBody response
154+ status_code = statusCode status
155+ ct <- case lookup "Content-Type" $ Client.responseHeaders response of
156+ Nothing -> pure $ "application"//"octet-stream"
157+ Just t -> case parseAccept t of
158+ Nothing -> left $ InvalidContentTypeHeader (cs t) body
159+ Just t' -> pure t'
160+ unless (isWantedStatus status_code) $
161+ left $ FailureResponse status ct body
162+ return (status_code, body, ct, response)
163+
164+performRequestCT :: MimeUnrender ct result =>
165+ Proxy ct -> Method -> Req -> [Int] -> BaseUrl -> EitherT ServantError IO result
166+performRequestCT ct reqMethod req wantedStatus reqHost = do
167+ let acceptCT = contentType ct
168+ (_status, respBody, respCT, _response) <-
169+ performRequest reqMethod (req { reqAccept = [acceptCT] }) (`elem` wantedStatus) reqHost
170+ unless (matches respCT (acceptCT)) $
171+ left $ UnsupportedContentType respCT respBody
172+ either
173+ (left . (\s -> DecodeFailure s respCT respBody))
174+ return
175+ (mimeUnrender ct respBody)
176+
177+performRequestNoBody :: Method -> Req -> [Int] -> BaseUrl -> EitherT ServantError IO ()
178+performRequestNoBody reqMethod req wantedStatus reqHost = do
179+ _ <- performRequest reqMethod req (`elem` wantedStatus) reqHost
180+ return ()
181+
182+catchHttpException :: IO a -> IO (Either HttpException a)
183+catchHttpException action =
184+ catch (Right <$> action) (pure . Left)
servant-client/test/Servant/ClientSpec.hsnew file+345
@@ -0,0 +1,345 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE GADTs #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE RecordWildCards #-}
7+{-# LANGUAGE StandaloneDeriving #-}
8+{-# LANGUAGE TypeOperators #-}
9+{-# OPTIONS_GHC -fcontext-stack=25 #-}
10+{-# OPTIONS_GHC -fno-warn-orphans #-}
11+module Servant.ClientSpec where
12+
13+import Control.Applicative
14+import qualified Control.Arrow as Arrow
15+import Control.Concurrent
16+import Control.Exception
17+import Control.Monad.Trans.Either
18+import Data.Aeson
19+import Data.ByteString.Lazy (ByteString)
20+import Data.Char
21+import Data.Foldable (forM_)
22+import Data.Monoid
23+import Data.Proxy
24+import qualified Data.Text as T
25+import GHC.Generics
26+import qualified Network.HTTP.Client as C
27+import Network.HTTP.Media
28+import Network.HTTP.Types
29+import Network.Socket
30+import Network.Wai hiding (Response)
31+import Network.Wai.Handler.Warp
32+import Test.Hspec
33+import Test.Hspec.QuickCheck
34+import Test.HUnit
35+import Test.QuickCheck
36+
37+import Servant.API
38+import Servant.Client
39+import Servant.Server
40+
41+-- * test data types
42+
43+data Person = Person {
44+ name :: String,
45+ age :: Integer
46+ }
47+ deriving (Eq, Show, Generic)
48+
49+instance ToJSON Person
50+instance FromJSON Person
51+
52+instance ToFormUrlEncoded Person where
53+ toFormUrlEncoded Person{..} =
54+ [("name", T.pack name), ("age", T.pack (show age))]
55+
56+lookupEither :: (Show a, Eq a) => a -> [(a,b)] -> Either String b
57+lookupEither x xs = do
58+ maybe (Left $ "could not find key " <> show x) return $ lookup x xs
59+
60+instance FromFormUrlEncoded Person where
61+ fromFormUrlEncoded xs = do
62+ n <- lookupEither "name" xs
63+ a <- lookupEither "age" xs
64+ return $ Person (T.unpack n) (read $ T.unpack a)
65+
66+deriving instance Eq ServantError
67+
68+instance Eq C.HttpException where
69+ a == b = show a == show b
70+
71+alice :: Person
72+alice = Person "Alice" 42
73+
74+type Api =
75+ "get" :> Get '[JSON] Person
76+ :<|> "delete" :> Delete
77+ :<|> "capture" :> Capture "name" String :> Get '[JSON,FormUrlEncoded] Person
78+ :<|> "body" :> ReqBody '[FormUrlEncoded,JSON] Person :> Post '[JSON] Person
79+ :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person
80+ :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
81+ :<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool
82+ :<|> "matrixparam" :> MatrixParam "name" String :> Get '[JSON] Person
83+ :<|> "matrixparams" :> MatrixParams "name" String :> Get '[JSON] [Person]
84+ :<|> "matrixflag" :> MatrixFlag "flag" :> Get '[JSON] Bool
85+ :<|> "rawSuccess" :> Raw
86+ :<|> "rawFailure" :> Raw
87+ :<|> "multiple" :>
88+ Capture "first" String :>
89+ QueryParam "second" Int :>
90+ QueryFlag "third" :>
91+ ReqBody '[JSON] [(String, [Rational])] :>
92+ Get '[JSON] (String, Maybe Int, Bool, [(String, [Rational])])
93+api :: Proxy Api
94+api = Proxy
95+
96+server :: Application
97+server = serve api (
98+ return alice
99+ :<|> return ()
100+ :<|> (\ name -> return $ Person name 0)
101+ :<|> return
102+ :<|> (\ name -> case name of
103+ Just "alice" -> return alice
104+ Just name -> left (400, name ++ " not found")
105+ Nothing -> left (400, "missing parameter"))
106+ :<|> (\ names -> return (zipWith Person names [0..]))
107+ :<|> return
108+ :<|> (\ name -> case name of
109+ Just "alice" -> return alice
110+ Just name -> left (400, name ++ " not found")
111+ Nothing -> left (400, "missing parameter"))
112+ :<|> (\ names -> return (zipWith Person names [0..]))
113+ :<|> return
114+ :<|> (\ _request respond -> respond $ responseLBS ok200 [] "rawSuccess")
115+ :<|> (\ _request respond -> respond $ responseLBS badRequest400 [] "rawFailure")
116+ :<|> \ a b c d -> return (a, b, c, d)
117+ )
118+
119+withServer :: (BaseUrl -> IO a) -> IO a
120+withServer action = withWaiDaemon (return server) action
121+
122+getGet :: BaseUrl -> EitherT ServantError IO Person
123+getDelete :: BaseUrl -> EitherT ServantError IO ()
124+getCapture :: String -> BaseUrl -> EitherT ServantError IO Person
125+getBody :: Person -> BaseUrl -> EitherT ServantError IO Person
126+getQueryParam :: Maybe String -> BaseUrl -> EitherT ServantError IO Person
127+getQueryParams :: [String] -> BaseUrl -> EitherT ServantError IO [Person]
128+getQueryFlag :: Bool -> BaseUrl -> EitherT ServantError IO Bool
129+getMatrixParam :: Maybe String -> BaseUrl -> EitherT ServantError IO Person
130+getMatrixParams :: [String] -> BaseUrl -> EitherT ServantError IO [Person]
131+getMatrixFlag :: Bool -> BaseUrl -> EitherT ServantError IO Bool
132+getRawSuccess :: Method -> BaseUrl -> EitherT ServantError IO (Int, ByteString, MediaType, C.Response ByteString)
133+getRawFailure :: Method -> BaseUrl -> EitherT ServantError IO (Int, ByteString, MediaType, C.Response ByteString)
134+getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
135+ -> BaseUrl
136+ -> EitherT ServantError IO (String, Maybe Int, Bool, [(String, [Rational])])
137+( getGet
138+ :<|> getDelete
139+ :<|> getCapture
140+ :<|> getBody
141+ :<|> getQueryParam
142+ :<|> getQueryParams
143+ :<|> getQueryFlag
144+ :<|> getMatrixParam
145+ :<|> getMatrixParams
146+ :<|> getMatrixFlag
147+ :<|> getRawSuccess
148+ :<|> getRawFailure
149+ :<|> getMultiple)
150+ = client api
151+
152+type FailApi =
153+ "get" :> Raw
154+ :<|> "capture" :> Capture "name" String :> Raw
155+ :<|> "body" :> Raw
156+failApi :: Proxy FailApi
157+failApi = Proxy
158+
159+failServer :: Application
160+failServer = serve failApi (
161+ (\ _request respond -> respond $ responseLBS ok200 [] "")
162+ :<|> (\ _capture _request respond -> respond $ responseLBS ok200 [("content-type", "application/json")] "")
163+ :<|> (\_request respond -> respond $ responseLBS ok200 [("content-type", "fooooo")] "")
164+ )
165+
166+withFailServer :: (BaseUrl -> IO a) -> IO a
167+withFailServer action = withWaiDaemon (return failServer) action
168+
169+spec :: Spec
170+spec = do
171+ it "Servant.API.Get" $ withServer $ \ host -> do
172+ (Arrow.left show <$> runEitherT (getGet host)) `shouldReturn` Right alice
173+
174+ it "Servant.API.Delete" $ withServer $ \ host -> do
175+ (Arrow.left show <$> runEitherT (getDelete host)) `shouldReturn` Right ()
176+
177+ it "Servant.API.Capture" $ withServer $ \ host -> do
178+ (Arrow.left show <$> runEitherT (getCapture "Paula" host)) `shouldReturn` Right (Person "Paula" 0)
179+
180+ it "Servant.API.ReqBody" $ withServer $ \ host -> do
181+ let p = Person "Clara" 42
182+ (Arrow.left show <$> runEitherT (getBody p host)) `shouldReturn` Right p
183+
184+ it "Servant.API.QueryParam" $ withServer $ \ host -> do
185+ Arrow.left show <$> runEitherT (getQueryParam (Just "alice") host) `shouldReturn` Right alice
186+ Left FailureResponse{..} <- runEitherT (getQueryParam (Just "bob") host)
187+ responseStatus `shouldBe` Status 400 "bob not found"
188+
189+ it "Servant.API.QueryParam.QueryParams" $ withServer $ \ host -> do
190+ (Arrow.left show <$> runEitherT (getQueryParams [] host)) `shouldReturn` Right []
191+ (Arrow.left show <$> runEitherT (getQueryParams ["alice", "bob"] host))
192+ `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
193+
194+ context "Servant.API.QueryParam.QueryFlag" $
195+ forM_ [False, True] $ \ flag ->
196+ it (show flag) $ withServer $ \ host -> do
197+ (Arrow.left show <$> runEitherT (getQueryFlag flag host)) `shouldReturn` Right flag
198+
199+ it "Servant.API.MatrixParam" $ withServer $ \ host -> do
200+ Arrow.left show <$> runEitherT (getMatrixParam (Just "alice") host) `shouldReturn` Right alice
201+ Left FailureResponse{..} <- runEitherT (getMatrixParam (Just "bob") host)
202+ responseStatus `shouldBe` Status 400 "bob not found"
203+
204+ it "Servant.API.MatrixParam.MatrixParams" $ withServer $ \ host -> do
205+ Arrow.left show <$> runEitherT (getMatrixParams [] host) `shouldReturn` Right []
206+ Arrow.left show <$> runEitherT (getMatrixParams ["alice", "bob"] host)
207+ `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
208+
209+ context "Servant.API.MatrixParam.MatrixFlag" $
210+ forM_ [False, True] $ \ flag ->
211+ it (show flag) $ withServer $ \ host -> do
212+ Arrow.left show <$> runEitherT (getMatrixFlag flag host) `shouldReturn` Right flag
213+
214+ it "Servant.API.Raw on success" $ withServer $ \ host -> do
215+ res <- runEitherT (getRawSuccess methodGet host)
216+ case res of
217+ Left e -> assertFailure $ show e
218+ Right (code, body, ct, response) -> do
219+ (code, body, ct) `shouldBe` (200, "rawSuccess", "application"//"octet-stream")
220+ C.responseBody response `shouldBe` body
221+ C.responseStatus response `shouldBe` ok200
222+
223+ it "Servant.API.Raw on failure" $ withServer $ \ host -> do
224+ res <- runEitherT (getRawFailure methodGet host)
225+ case res of
226+ Left e -> assertFailure $ show e
227+ Right (code, body, ct, response) -> do
228+ (code, body, ct) `shouldBe` (400, "rawFailure", "application"//"octet-stream")
229+ C.responseBody response `shouldBe` body
230+ C.responseStatus response `shouldBe` badRequest400
231+
232+ modifyMaxSuccess (const 20) $ do
233+ it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $
234+ property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
235+ ioProperty $ do
236+ withServer $ \ host -> do
237+ result <- Arrow.left show <$> runEitherT (getMultiple cap num flag body host)
238+ return $
239+ result === Right (cap, num, flag, body)
240+
241+
242+ context "client correctly handles error status codes" $ do
243+ let test :: (WrappedApi, String) -> Spec
244+ test (WrappedApi api, desc) =
245+ it desc $
246+ withWaiDaemon (return (serve api (left (500, "error message")))) $
247+ \ host -> do
248+ let getResponse :: BaseUrl -> EitherT ServantError IO ()
249+ getResponse = client api
250+ Left FailureResponse{..} <- runEitherT (getResponse host)
251+ responseStatus `shouldBe` (Status 500 "error message")
252+ mapM_ test $
253+ (WrappedApi (Proxy :: Proxy Delete), "Delete") :
254+ (WrappedApi (Proxy :: Proxy (Get '[JSON] ())), "Get") :
255+ (WrappedApi (Proxy :: Proxy (Post '[JSON] ())), "Post") :
256+ (WrappedApi (Proxy :: Proxy (Put '[JSON] ())), "Put") :
257+ []
258+
259+ context "client returns errors appropriately" $ do
260+ it "reports FailureResponse" $ withFailServer $ \ host -> do
261+ Left res <- runEitherT (getDelete host)
262+ case res of
263+ FailureResponse (Status 404 "Not Found") _ _ -> return ()
264+ _ -> fail $ "expected 404 response, but got " <> show res
265+
266+ it "reports DecodeFailure" $ withFailServer $ \ host -> do
267+ Left res <- runEitherT (getCapture "foo" host)
268+ case res of
269+ DecodeFailure _ ("application/json") _ -> return ()
270+ _ -> fail $ "expected DecodeFailure, but got " <> show res
271+
272+ it "reports ConnectionError" $ do
273+ Right host <- return $ parseBaseUrl "127.0.0.1:987654"
274+ Left res <- runEitherT (getGet host)
275+ case res of
276+ ConnectionError (C.FailedConnectionException2 "127.0.0.1" 987654 False _) -> return ()
277+ _ -> fail $ "expected ConnectionError, but got " <> show res
278+
279+ it "reports UnsupportedContentType" $ withFailServer $ \ host -> do
280+ Left res <- runEitherT (getGet host)
281+ case res of
282+ UnsupportedContentType ("application/octet-stream") _ -> return ()
283+ _ -> fail $ "expected UnsupportedContentType, but got " <> show res
284+
285+ it "reports InvalidContentTypeHeader" $ withFailServer $ \ host -> do
286+ Left res <- runEitherT (getBody alice host)
287+ case res of
288+ InvalidContentTypeHeader "fooooo" _ -> return ()
289+ _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
290+
291+data WrappedApi where
292+ WrappedApi :: (HasServer (Canonicalize api), Server api ~ EitherT (Int, String) IO a,
293+ HasClient (Canonicalize api), Client api ~ (BaseUrl -> EitherT ServantError IO ())) =>
294+ Proxy api -> WrappedApi
295+
296+
297+-- * utils
298+
299+withWaiDaemon :: IO Application -> (BaseUrl -> IO a) -> IO a
300+withWaiDaemon mkApplication action = do
301+ application <- mkApplication
302+ bracket (acquire application) free (\ (_, _, baseUrl) -> action baseUrl)
303+ where
304+ acquire application = do
305+ (notifyStart, waitForStart) <- lvar
306+ (notifyKilled, waitForKilled) <- lvar
307+ thread <- forkIO $ (do
308+ (krakenPort, socket) <- openTestSocket
309+ let settings =
310+ setPort krakenPort $ -- set here just for consistency, shouldn't be
311+ -- used (it's set in the socket)
312+ setBeforeMainLoop (notifyStart krakenPort)
313+ defaultSettings
314+ runSettingsSocket settings socket application)
315+ `finally` notifyKilled ()
316+ krakenPort <- waitForStart
317+ let baseUrl = (BaseUrl Http "localhost" 80){baseUrlPort = krakenPort}
318+ return (thread, waitForKilled, baseUrl)
319+ free (thread, waitForKilled, _) = do
320+ killThread thread
321+ waitForKilled
322+
323+ lvar :: IO (a -> IO (), IO a)
324+ lvar = do
325+ mvar <- newEmptyMVar
326+ let put = putMVar mvar
327+ wait = readMVar mvar
328+ return (put, wait)
329+
330+openTestSocket :: IO (Port, Socket)
331+openTestSocket = do
332+ s <- socket AF_INET Stream defaultProtocol
333+ localhost <- inet_addr "127.0.0.1"
334+ bind s (SockAddrInet aNY_PORT localhost)
335+ listen s 1
336+ port <- socketPort s
337+ return (fromIntegral port, s)
338+
339+pathGen :: Gen (NonEmptyList Char)
340+pathGen = fmap NonEmpty path
341+ where
342+ path = listOf1 $ elements $
343+ filter (not . (`elem` "?%[]/#;")) $
344+ filter isPrint $
345+ map chr [0..127]
servant-client/test/Servant/Common/BaseUrlSpec.hsnew file+69
@@ -0,0 +1,69 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+module Servant.Common.BaseUrlSpec where
3+
4+import Control.Applicative
5+import Control.DeepSeq
6+import Test.Hspec
7+import Test.QuickCheck
8+
9+import Servant.Common.BaseUrl
10+
11+spec :: Spec
12+spec = do
13+ describe "showBaseUrl" $ do
14+ it "shows a BaseUrl" $ do
15+ showBaseUrl (BaseUrl Http "foo.com" 80) `shouldBe` "http://foo.com"
16+
17+ it "shows a https BaseUrl" $ do
18+ showBaseUrl (BaseUrl Https "foo.com" 443) `shouldBe` "https://foo.com"
19+
20+ describe "httpBaseUrl" $ do
21+ it "allows to construct default http BaseUrls" $ do
22+ BaseUrl Http "bar" 80 `shouldBe` BaseUrl Http "bar" 80
23+
24+ describe "parseBaseUrl" $ do
25+ it "is total" $ do
26+ property $ \ string ->
27+ deepseq (fmap show (parseBaseUrl string)) True
28+
29+ it "is the inverse of showBaseUrl" $ do
30+ property $ \ baseUrl ->
31+ counterexample (showBaseUrl baseUrl) $
32+ parseBaseUrl (showBaseUrl baseUrl) ===
33+ Right baseUrl
34+
35+ it "allows trailing slashes" $ do
36+ parseBaseUrl "foo.com/" `shouldBe` Right (BaseUrl Http "foo.com" 80)
37+
38+ context "urls without scheme" $ do
39+ it "assumes http" $ do
40+ parseBaseUrl "foo.com" `shouldBe` Right (BaseUrl Http "foo.com" 80)
41+
42+ it "allows port numbers" $ do
43+ parseBaseUrl "foo.com:8080" `shouldBe` Right (BaseUrl Http "foo.com" 8080)
44+
45+ it "rejects ftp urls" $ do
46+ parseBaseUrl "ftp://foo.com" `shouldSatisfy` isLeft
47+
48+instance Arbitrary BaseUrl where
49+ arbitrary = BaseUrl <$>
50+ elements [Http, Https] <*>
51+ hostNameGen <*>
52+ portGen
53+ where
54+ -- this does not perfectly mirror the url standard, but I hope it's good
55+ -- enough.
56+ hostNameGen = do
57+ let letters = ['a' .. 'z'] ++ ['A' .. 'Z']
58+ first <- elements letters
59+ middle <- listOf1 $ elements (letters ++ ['0' .. '9'] ++ ['.', '-'])
60+ last <- elements letters
61+ return (first : middle ++ [last])
62+ portGen = frequency $
63+ (1, return 80) :
64+ (1, return 443) :
65+ (1, choose (1, 20000)) :
66+ []
67+
68+isLeft :: Either a b -> Bool
69+isLeft = either (const True) (const False)
test/Spec.hs → servant-client/test/Spec.hsrenamed

The content is the same.

servant-docs/CHANGELOG.mdnew file+15
@@ -0,0 +1,15 @@
1+0.4
2+---
3+* Allow for extra information to be added to the docs
4+* Support content-type aware combinators of *servant-0.3*
5+* Render endpoints in a canonical order (https://github.com/haskell-servant/servant-docs/pull/15)
6+* Remove ToJSON superclass from ToSample
7+* Split out Internal module
8+* `Canonicalize` API types before generating the docs for them
9+
10+0.3
11+---
12+
13+* Add the ability to display multiple responses, with some accompanying `Text` to describe the context in which we get the corresponding JSON.
14+* Expose the `headers` lens
15+* Represent an endpoint's path as `[String]` (previously `String`), fixing a corner case where the leading `/` would be missing.
servant-docs/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2014, Zalora South East Asia Pte Ltd
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Zalora South East Asia Pte Ltd nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-docs/README.mdnew file+72
@@ -0,0 +1,72 @@
1+# servant-docs
2+
3+[![Build Status](https://secure.travis-ci.org/haskell-servant/servant-docs.svg)](http://travis-ci.org/haskell-servant/servant-docs)
4+
5+![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
6+
7+Generate API docs for your *servant* webservice. Feel free to also take a look at [servant-pandoc](https://github.com/mpickering/servant-pandoc) which uses this package to target a broad range of output formats using the excellent **pandoc**.
8+
9+## Example
10+
11+See [here](https://github.com/haskell-servant/servant-docs/blob/master/example/greet.md) for the output of the following program.
12+
13+``` haskell
14+{-# LANGUAGE DataKinds #-}
15+{-# LANGUAGE PolyKinds #-}
16+{-# LANGUAGE TypeFamilies #-}
17+{-# LANGUAGE DeriveGeneric #-}
18+{-# LANGUAGE TypeOperators #-}
19+{-# LANGUAGE FlexibleInstances #-}
20+{-# LANGUAGE OverloadedStrings #-}
21+
22+import Data.Proxy
23+import Data.Text
24+import Servant
25+
26+-- our type for a Greeting message
27+data Greet = Greet { _msg :: Text }
28+ deriving (Generic, Show)
29+
30+-- we get our JSON serialization for free. This will be used by the default
31+-- 'MimeRender' instance for 'JSON'.
32+instance FromJSON Greet
33+instance ToJSON Greet
34+
35+-- We can also implement 'MimeRender' explicitly for additional formats.
36+instance MimeRender PlainText Greet where
37+ toByteString Proxy (Greet s) = "<h1>" <> cs s <> "</h1>"
38+
39+-- we provide a sample value for the 'Greet' type
40+instance ToSample Greet where
41+ toSample = Just g
42+
43+ where g = Greet "Hello, haskeller!"
44+
45+instance ToParam (QueryParam "capital" Bool) where
46+ toParam _ =
47+ DocQueryParam "capital"
48+ ["true", "false"]
49+ "Get the greeting message in uppercase (true) or not (false). Default is false."
50+
51+instance ToCapture (Capture "name" Text) where
52+ toCapture _ = DocCapture "name" "name of the person to greet"
53+
54+instance ToCapture (Capture "greetid" Text) where
55+ toCapture _ = DocCapture "greetid" "identifier of the greet msg to remove"
56+
57+-- API specification
58+type TestApi =
59+ "hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON,PlainText] Greet
60+ :<|> "greet" :> RQBody '[JSON] Greet :> Post '[JSON] Greet
61+ :<|> "delete" :> Capture "greetid" Text :> Delete
62+
63+testApi :: Proxy TestApi
64+testApi = Proxy
65+
66+-- Generate the Documentation's ADT
67+greetDocs :: API
68+greetDocs = docs testApi
69+
70+main :: IO ()
71+main = putStrLn $ markdown greetDocs
72+```
servant-docs/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
servant-docs/default.nixnew file+19
@@ -0,0 +1,19 @@
1+{ mkDerivation, aeson, base, bytestring, hashable, hspec
2+, http-media, lens, servant, stdenv, string-conversions, text
3+, unordered-containers
4+}:
5+mkDerivation {
6+ pname = "servant-docs";
7+ version = "0.3";
8+ src = ./.;
9+ isLibrary = true;
10+ isExecutable = true;
11+ buildDepends = [
12+ aeson base bytestring hashable http-media lens servant
13+ string-conversions text unordered-containers
14+ ];
15+ testDepends = [ aeson base hspec lens servant ];
16+ homepage = "http://haskell-servant.github.io/";
17+ description = "generate API docs for your servant webservice";
18+ license = stdenv.lib.licenses.bsd3;
19+}
servant-docs/docs.shnew file+52
@@ -0,0 +1,52 @@
1+SERVANT_DIR=/tmp/servant-docs-gh-pages
2+
3+# Make a temporary clone
4+
5+rm -rf $SERVANT_DIR
6+
7+git clone . $SERVANT_DIR
8+
9+cd $SERVANT_DIR
10+
11+# Make sure to pull the latest
12+
13+git remote add haskell-servant git@github.com:haskell-servant/servant-docs.git
14+
15+git fetch haskell-servant
16+
17+git reset --hard haskell-servant/gh-pages
18+
19+# Clear everything away
20+
21+git rm -rf $SERVANT_DIR/*
22+
23+# Switch back and build the haddocks
24+
25+cd -
26+
27+cabal configure --builddir=$SERVANT_DIR
28+
29+cabal haddock --hoogle --hyperlink-source --html-location='https://hackage.haskell.org/package/$pkg-$version/docs' --builddir=$SERVANT_DIR
30+
31+commit_hash=$(git rev-parse HEAD)
32+
33+# Move the HTML docs to the root
34+
35+cd $SERVANT_DIR
36+
37+rm *
38+rm -rf build
39+mv doc/html/servant-docs/* .
40+rm -r doc/
41+
42+# Add everything
43+
44+git add .
45+
46+git commit -m "Built from $commit_hash"
47+
48+# Push to update the pages
49+
50+git push haskell-servant HEAD:gh-pages
51+
52+rm -rf $SERVANT_DIR
servant-docs/example/greet.hsnew file+117
@@ -0,0 +1,117 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE MultiParamTypeClasses #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE TypeOperators #-}
7+{-# OPTIONS_GHC -fno-warn-orphans #-}
8+import Control.Lens
9+import Data.Aeson
10+import Data.Proxy
11+import Data.String.Conversions
12+import Data.Text (Text)
13+import GHC.Generics
14+import Servant.API
15+import Servant.Docs
16+
17+-- * Example
18+
19+-- | A greet message data type
20+newtype Greet = Greet Text
21+ deriving (Generic, Show)
22+
23+-- | We can get JSON support automatically. This will be used to parse
24+-- and encode a Greeting as 'JSON'.
25+instance FromJSON Greet
26+instance ToJSON Greet
27+
28+-- | We can also implement 'MimeRender' for additional formats like 'PlainText'.
29+instance MimeRender PlainText Greet where
30+ mimeRender Proxy (Greet s) = "\"" <> cs s <> "\""
31+
32+-- We add some useful annotations to our captures,
33+-- query parameters and request body to make the docs
34+-- really helpful.
35+instance ToCapture (Capture "name" Text) where
36+ toCapture _ = DocCapture "name" "name of the person to greet"
37+
38+instance ToCapture (Capture "greetid" Text) where
39+ toCapture _ = DocCapture "greetid" "identifier of the greet msg to remove"
40+
41+instance ToParam (QueryParam "capital" Bool) where
42+ toParam _ =
43+ DocQueryParam "capital"
44+ ["true", "false"]
45+ "Get the greeting message in uppercase (true) or not (false).\
46+ \Default is false."
47+ Normal
48+
49+instance ToParam (MatrixParam "lang" String) where
50+ toParam _ =
51+ DocQueryParam "lang"
52+ ["en", "sv", "fr"]
53+ "Get the greeting message selected language. Default is en."
54+ Normal
55+
56+instance ToSample Greet where
57+ toSample = Just $ Greet "Hello, haskeller!"
58+
59+ toSamples =
60+ [ ("If you use ?capital=true", Greet "HELLO, HASKELLER")
61+ , ("If you use ?capital=false", Greet "Hello, haskeller")
62+ ]
63+
64+-- We define some introductory sections, these will appear at the top of the
65+-- documentation.
66+--
67+-- We pass them in with 'docsWith', below. If you only want to add
68+-- introductions, you may use 'docsWithIntros'
69+intro1 :: DocIntro
70+intro1 = DocIntro "On proper introductions." -- The title
71+ [ "Hello there."
72+ , "As documentation is usually written for humans, it's often useful \
73+ \to introduce concepts with a few words." ] -- Elements are paragraphs
74+
75+intro2 :: DocIntro
76+intro2 = DocIntro "This title is below the last"
77+ [ "You'll also note that multiple intros are possible." ]
78+
79+
80+-- API specification
81+type TestApi =
82+ -- GET /hello/:name?capital={true, false} returns a Greet as JSON or PlainText
83+ "hello" :> MatrixParam "lang" String :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet
84+
85+ -- POST /greet with a Greet as JSON in the request body,
86+ -- returns a Greet as JSON
87+ :<|> "greet" :> ReqBody '[JSON] Greet :> Post '[JSON] Greet
88+
89+ -- DELETE /greet/:greetid
90+ :<|> "greet" :> Capture "greetid" Text :> Delete
91+
92+testApi :: Proxy TestApi
93+testApi = Proxy
94+
95+-- Build some extra information for the DELETE /greet/:greetid endpoint. We
96+-- want to add documentation about a secret unicorn header and some extra
97+-- notes.
98+extra :: ExtraInfo TestApi
99+extra =
100+ extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $
101+ defAction & headers <>~ ["unicorns"]
102+ & notes <>~ [ DocNote "Title" ["This is some text"]
103+ , DocNote "Second secton" ["And some more"]
104+ ]
105+
106+-- Generate the data that lets us have API docs. This
107+-- is derived from the type as well as from
108+-- the 'ToCapture', 'ToParam' and 'ToSample' instances from above.
109+--
110+-- If you didn't want intros and extra information, you could just call:
111+--
112+-- > docs testAPI :: API
113+docsGreet :: API
114+docsGreet = docsWith [intro1, intro2] extra testApi
115+
116+main :: IO ()
117+main = putStrLn $ markdown docsGreet
servant-docs/example/greet.mdnew file+124
@@ -0,0 +1,124 @@
1+#### On proper introductions.
2+
3+Hello there.
4+
5+As documentation is usually written for humans, it's often useful to introduce concepts with a few words.
6+
7+#### This title is below the last
8+
9+You'll also note that multiple intros are possible.
10+
11+## POST /greet
12+
13+#### Request:
14+
15+- Supported content types are:
16+
17+ - `application/json`
18+
19+- Example: `application/json`
20+
21+```javascript
22+"Hello, haskeller!"
23+```
24+
25+#### Response:
26+
27+- Status code 201
28+
29+- Supported content types are:
30+
31+ - `application/json`
32+
33+- If you use ?capital=true
34+
35+```javascript
36+"HELLO, HASKELLER"
37+```
38+
39+- If you use ?capital=false
40+
41+```javascript
42+"Hello, haskeller"
43+```
44+
45+## GET /hello;lang=<value>/:name
46+
47+#### Captures:
48+
49+- *name*: name of the person to greet
50+
51+#### Matrix Parameters:
52+
53+**hello**:
54+
55+- lang
56+ - **Values**: *en, sv, fr*
57+ - **Description**: Get the greeting message selected language. Default is en.
58+
59+
60+
61+#### GET Parameters:
62+
63+- capital
64+ - **Values**: *true, false*
65+ - **Description**: Get the greeting message in uppercase (true) or not (false).Default is false.
66+
67+
68+#### Response:
69+
70+- Status code 200
71+
72+- Supported content types are:
73+
74+ - `application/json`
75+ - `text/plain;charset=utf-8`
76+
77+- If you use ?capital=true
78+
79+```javascript
80+"HELLO, HASKELLER"
81+```
82+
83+- If you use ?capital=true
84+
85+```
86+"HELLO, HASKELLER"
87+```
88+
89+- If you use ?capital=false
90+
91+```javascript
92+"Hello, haskeller"
93+```
94+
95+- If you use ?capital=false
96+
97+```
98+"Hello, haskeller"
99+```
100+
101+## DELETE /greet/:greetid
102+
103+#### Title
104+
105+This is some text
106+
107+#### Second secton
108+
109+And some more
110+
111+#### Captures:
112+
113+- *greetid*: identifier of the greet msg to remove
114+
115+
116+- This endpoint is sensitive to the value of the **unicorns** HTTP header.
117+
118+#### Response:
119+
120+- Status code 200
121+
122+- No response body
123+
124+
servant-docs/servant-docs.cabalnew file+71
@@ -0,0 +1,71 @@
1+name: servant-docs
2+version: 0.3
3+synopsis: generate API docs for your servant webservice
4+description:
5+ Library for generating API docs from a servant API definition.
6+ .
7+ Runnable example <https://github.com/haskell-servant/servant-docs/blob/master/example/greet.hs here>.
8+license: BSD3
9+license-file: LICENSE
10+author: Alp Mestanogullari, Sönke Hahn, Julian K. Arni
11+maintainer: alpmestan@gmail.com
12+copyright: 2014-2015 Zalora South East Asia Pte Ltd
13+category: Web
14+build-type: Simple
15+cabal-version: >=1.10
16+tested-with: GHC >= 7.8
17+homepage: http://haskell-servant.github.io/
18+Bug-reports: http://github.com/haskell-servant/servant-docs/issues
19+extra-source-files:
20+ CHANGELOG.md
21+ README.md
22+source-repository head
23+ type: git
24+ location: http://github.com/haskell-servant/servant-docs.git
25+
26+library
27+ exposed-modules:
28+ Servant.Docs
29+ , Servant.Docs.Internal
30+ build-depends:
31+ base >=4.7 && <5
32+ , bytestring
33+ , hashable
34+ , http-media >= 0.6
35+ , lens
36+ , servant >= 0.2.1
37+ , string-conversions
38+ , text
39+ , unordered-containers
40+ hs-source-dirs: src
41+ default-language: Haskell2010
42+ ghc-options: -Wall
43+
44+executable greet-docs
45+ main-is: greet.hs
46+ hs-source-dirs: example
47+ ghc-options: -Wall
48+ build-depends:
49+ base
50+ , aeson
51+ , lens
52+ , servant
53+ , servant-docs
54+ , string-conversions
55+ , text
56+ default-language: Haskell2010
57+
58+test-suite spec
59+ type: exitcode-stdio-1.0
60+ main-is: Spec.hs
61+ hs-source-dirs: test
62+ ghc-options: -Wall
63+ build-depends:
64+ base
65+ , aeson
66+ , hspec
67+ , servant
68+ , servant-docs
69+ , string-conversions
70+ default-language: Haskell2010
71+
servant-docs/shell.nixnew file+9
@@ -0,0 +1,9 @@
1+with (import <nixpkgs> {}).pkgs;
2+let modifiedHaskellPackages = haskellngPackages.override {
3+ overrides = self: super: {
4+ servant = self.callPackage ../servant {};
5+ servant-server = self.callPackage ./servant-server {};
6+ servant-docs = self.callPackage ./. {};
7+ };
8+ };
9+in modifiedHaskellPackages.servant-docs.env
servant-docs/src/Servant/Docs.hsnew file+168
@@ -0,0 +1,168 @@
1+-------------------------------------------------------------------------------
2+-- | This module lets you get API docs for free. It lets you generate
3+-- an 'API' from the type that represents your API using 'docs':
4+--
5+-- @docs :: 'HasDocs' api => 'Proxy' api -> 'API'@
6+--
7+-- Alternatively, if you wish to add one or more introductions to your
8+-- documentation, use 'docsWithIntros':
9+--
10+-- @'docsWithIntros' :: 'HasDocs' api => [DocIntro] -> 'Proxy' api -> 'API'@
11+--
12+-- You can then call 'markdown' on the 'API' value:
13+--
14+-- @'markdown' :: 'API' -> String@
15+--
16+-- or define a custom pretty printer:
17+--
18+-- @yourPrettyDocs :: 'API' -> String -- or blaze-html's HTML, or ...@
19+--
20+-- The only thing you'll need to do will be to implement some classes
21+-- for your captures, get parameters and request or response bodies.
22+--
23+-- Here is a complete example that you can run to see the markdown pretty
24+-- printer in action:
25+--
26+-- > {-# LANGUAGE DataKinds #-}
27+-- > {-# LANGUAGE DeriveGeneric #-}
28+-- > {-# LANGUAGE FlexibleInstances #-}
29+-- > {-# LANGUAGE MultiParamTypeClasses #-}
30+-- > {-# LANGUAGE OverloadedStrings #-}
31+-- > {-# LANGUAGE TypeOperators #-}
32+-- > {-# OPTIONS_GHC -fno-warn-orphans #-}
33+-- > import Data.Aeson
34+-- > import Data.Proxy
35+-- > import Data.String.Conversions
36+-- > import Data.Text (Text)
37+-- > import GHC.Generics
38+-- > import Servant.API
39+-- > import Servant.Docs
40+-- >
41+-- > -- * Example
42+-- >
43+-- > -- | A greet message data type
44+-- > newtype Greet = Greet Text
45+-- > deriving (Generic, Show)
46+-- >
47+-- > -- | We can get JSON support automatically. This will be used to parse
48+-- > -- and encode a Greeting as 'JSON'.
49+-- > instance FromJSON Greet
50+-- > instance ToJSON Greet
51+-- >
52+-- > -- | We can also implement 'MimeRender' for additional formats like 'PlainText'.
53+-- > instance MimeRender PlainText Greet where
54+-- > toByteString Proxy (Greet s) = "\"" <> cs s <> "\""
55+-- >
56+-- > -- We add some useful annotations to our captures,
57+-- > -- query parameters and request body to make the docs
58+-- > -- really helpful.
59+-- > instance ToCapture (Capture "name" Text) where
60+-- > toCapture _ = DocCapture "name" "name of the person to greet"
61+-- >
62+-- > instance ToCapture (Capture "greetid" Text) where
63+-- > toCapture _ = DocCapture "greetid" "identifier of the greet msg to remove"
64+-- >
65+-- > instance ToParam (QueryParam "capital" Bool) where
66+-- > toParam _ =
67+-- > DocQueryParam "capital"
68+-- > ["true", "false"]
69+-- > "Get the greeting message in uppercase (true) or not (false).\
70+-- > \Default is false."
71+-- > Normal
72+-- >
73+-- > instance ToParam (MatrixParam "lang" String) where
74+-- > toParam _ =
75+-- > DocQueryParam "lang"
76+-- > ["en", "sv", "fr"]
77+-- > "Get the greeting message selected language. Default is en."
78+-- > Normal
79+-- >
80+-- > instance ToSample Greet where
81+-- > toSample = Just $ Greet "Hello, haskeller!"
82+-- >
83+-- > toSamples =
84+-- > [ ("If you use ?capital=true", Greet "HELLO, HASKELLER")
85+-- > , ("If you use ?capital=false", Greet "Hello, haskeller")
86+-- > ]
87+-- >
88+-- > -- We define some introductory sections, these will appear at the top of the
89+-- > -- documentation.
90+-- > --
91+-- > -- We pass them in with 'docsWith', below. If you only want to add
92+-- > -- introductions, you may use 'docsWithIntros'
93+-- > intro1 :: DocIntro
94+-- > intro1 = DocIntro "On proper introductions." -- The title
95+-- > [ "Hello there."
96+-- > , "As documentation is usually written for humans, it's often useful \
97+-- > \to introduce concepts with a few words." ] -- Elements are paragraphs
98+-- >
99+-- > intro2 :: DocIntro
100+-- > intro2 = DocIntro "This title is below the last"
101+-- > [ "You'll also note that multiple intros are possible." ]
102+-- >
103+-- >
104+-- > -- API specification
105+-- > type TestApi =
106+-- > -- GET /hello/:name?capital={true, false} returns a Greet as JSON or PlainText
107+-- > "hello" :> MatrixParam "lang" String :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet
108+-- >
109+-- > -- POST /greet with a Greet as JSON in the request body,
110+-- > -- returns a Greet as JSON
111+-- > :<|> "greet" :> ReqBody '[JSON] Greet :> Post '[JSON] Greet
112+-- >
113+-- > -- DELETE /greet/:greetid
114+-- > :<|> "greet" :> Capture "greetid" Text :> Delete
115+-- >
116+-- > testApi :: Proxy TestApi
117+-- > testApi = Proxy
118+-- >
119+-- > -- Build some extra information for the DELETE /greet/:greetid endpoint. We
120+-- > -- want to add documentation about a secret unicorn header and some extra
121+-- > -- notes.
122+-- > extra :: ExtraInfo TestApi
123+-- > extra =
124+-- > extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $
125+-- > defAction & headers <>~ ["unicorns"]
126+-- > & notes <>~ [ DocNote "Title" ["This is some text"]
127+-- > , DocNote "Second secton" ["And some more"]
128+-- > ]
129+-- >
130+-- > -- Generate the data that lets us have API docs. This
131+-- > -- is derived from the type as well as from
132+-- > -- the 'ToCapture', 'ToParam' and 'ToSample' instances from above.
133+-- > --
134+-- > -- If you didn't want intros and extra information, you could just call:
135+-- > --
136+-- > -- > docs testAPI :: API
137+-- > docsGreet :: API
138+-- > docsGreet = docsWith [intro1, intro2] extra testApi
139+-- >
140+-- > main :: IO ()
141+-- > main = putStrLn $ markdown docsGreet
142+module Servant.Docs
143+ ( -- * 'HasDocs' class and key functions
144+ HasDocs(..), docs, markdown
145+ -- * Generating docs with extra information
146+ , ExtraInfo(..), docsWith, docsWithIntros, extraInfo
147+
148+ , -- * Classes you need to implement for your types
149+ ToSample(..)
150+ , sampleByteString
151+ , sampleByteStrings
152+ , ToParam(..)
153+ , ToCapture(..)
154+
155+ , -- * ADTs to represent an 'API'
156+ Method(..)
157+ , Endpoint, path, method, defEndpoint
158+ , API, emptyAPI
159+ , DocCapture(..), capSymbol, capDesc
160+ , DocQueryParam(..), ParamKind(..), paramName, paramValues, paramDesc, paramKind
161+ , DocNote(..), noteTitle, noteBody
162+ , DocIntro(..)
163+ , Response(..), respStatus, respTypes, respBody, defResponse
164+ , Action, captures, headers, notes, params, rqtypes, rqbody, response, defAction
165+ , single
166+ ) where
167+
168+import Servant.Docs.Internal
servant-docs/src/Servant/Docs/Internal.hsnew file+803
@@ -0,0 +1,803 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE DeriveGeneric #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE PolyKinds #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TemplateHaskell #-}
10+{-# LANGUAGE TupleSections #-}
11+{-# LANGUAGE TypeFamilies #-}
12+{-# LANGUAGE TypeOperators #-}
13+{-# LANGUAGE UndecidableInstances #-}
14+module Servant.Docs.Internal where
15+
16+import Control.Applicative
17+import Control.Lens
18+import Data.ByteString.Lazy.Char8 (ByteString)
19+import Data.Hashable
20+import Data.HashMap.Strict (HashMap)
21+import Data.List
22+import Data.Maybe
23+import Data.Monoid
24+import Data.Ord (comparing)
25+import Data.Proxy
26+import Data.String.Conversions
27+import Data.Text (Text, pack, unpack)
28+import GHC.Exts (Constraint)
29+import GHC.Generics
30+import GHC.TypeLits
31+import Servant.API
32+import Servant.API.ContentTypes
33+import Servant.Utils.Links
34+
35+import qualified Data.HashMap.Strict as HM
36+import qualified Data.Text as T
37+import qualified Network.HTTP.Media as M
38+
39+-- | Supported HTTP request methods
40+data Method = DocDELETE -- ^ the DELETE method
41+ | DocGET -- ^ the GET method
42+ | DocPOST -- ^ the POST method
43+ | DocPUT -- ^ the PUT method
44+ deriving (Eq, Ord, Generic)
45+
46+instance Show Method where
47+ show DocGET = "GET"
48+ show DocPOST = "POST"
49+ show DocDELETE = "DELETE"
50+ show DocPUT = "PUT"
51+
52+instance Hashable Method
53+
54+-- | An 'Endpoint' type that holds the 'path' and the 'method'.
55+--
56+-- Gets used as the key in the 'API' hashmap. Modify 'defEndpoint'
57+-- or any 'Endpoint' value you want using the 'path' and 'method'
58+-- lenses to tweak.
59+--
60+-- @
61+-- λ> 'defEndpoint'
62+-- GET /
63+-- λ> 'defEndpoint' & 'path' '<>~' ["foo"]
64+-- GET /foo
65+-- λ> 'defEndpoint' & 'path' '<>~' ["foo"] & 'method' '.~' 'DocPOST'
66+-- POST /foo
67+-- @
68+data Endpoint = Endpoint
69+ { _path :: [String] -- type collected
70+ , _method :: Method -- type collected
71+ } deriving (Eq, Ord, Generic)
72+
73+instance Show Endpoint where
74+ show (Endpoint p m) =
75+ show m ++ " " ++ showPath p
76+
77+-- |
78+-- Render a path as a '/'-delimited string
79+--
80+showPath :: [String] -> String
81+showPath [] = "/"
82+showPath ps = concatMap ('/' :) ps
83+
84+-- | An 'Endpoint' whose path is `"/"` and whose method is 'DocGET'
85+--
86+-- Here's how you can modify it:
87+--
88+-- @
89+-- λ> 'defEndpoint'
90+-- GET /
91+-- λ> 'defEndpoint' & 'path' '<>~' ["foo"]
92+-- GET /foo
93+-- λ> 'defEndpoint' & 'path' '<>~' ["foo"] & 'method' '.~' 'DocPOST'
94+-- POST /foo
95+-- @
96+defEndpoint :: Endpoint
97+defEndpoint = Endpoint [] DocGET
98+
99+instance Hashable Endpoint
100+
101+-- | Our API documentation type, a product of top-level information and a good
102+-- old hashmap from 'Endpoint' to 'Action'
103+data API = API
104+ { _apiIntros :: [DocIntro]
105+ , _apiEndpoints :: HashMap Endpoint Action
106+ } deriving (Eq, Show)
107+
108+instance Monoid API where
109+ API a1 b1 `mappend` API a2 b2 = API (a1 <> a2) (b1 <> b2)
110+ mempty = API mempty mempty
111+
112+-- | An empty 'API'
113+emptyAPI :: API
114+emptyAPI = mempty
115+
116+-- | A type to represent captures. Holds the name of the capture
117+-- and a description.
118+--
119+-- Write a 'ToCapture' instance for your captured types.
120+data DocCapture = DocCapture
121+ { _capSymbol :: String -- type supplied
122+ , _capDesc :: String -- user supplied
123+ } deriving (Eq, Ord, Show)
124+
125+-- | A type to represent a /GET/ parameter from the Query String. Holds its name,
126+-- the possible values (leave empty if there isn't a finite number of them),
127+-- and a description of how it influences the output or behavior.
128+--
129+-- Write a 'ToParam' instance for your GET parameter types
130+data DocQueryParam = DocQueryParam
131+ { _paramName :: String -- type supplied
132+ , _paramValues :: [String] -- user supplied
133+ , _paramDesc :: String -- user supplied
134+ , _paramKind :: ParamKind
135+ } deriving (Eq, Ord, Show)
136+
137+-- | An introductory paragraph for your documentation. You can pass these to
138+-- 'docsWithIntros'.
139+data DocIntro = DocIntro
140+ { _introTitle :: String -- ^ Appears above the intro blob
141+ , _introBody :: [String] -- ^ Each String is a paragraph.
142+ } deriving (Eq, Show)
143+
144+instance Ord DocIntro where
145+ compare = comparing _introTitle
146+
147+-- | A type to represent extra notes that may be attached to an 'Action'.
148+--
149+-- This is intended to be used when writing your own HasDocs instances to
150+-- add extra sections to your endpoint's documentation.
151+data DocNote = DocNote
152+ { _noteTitle :: String
153+ , _noteBody :: [String]
154+ } deriving (Eq, Ord, Show)
155+
156+-- | Type of extra information that a user may wish to "union" with their
157+-- documentation.
158+--
159+-- These are intended to be built using extraInfo.
160+-- Multiple ExtraInfo may be combined with the monoid instance.
161+newtype ExtraInfo layout = ExtraInfo (HashMap Endpoint Action)
162+instance Monoid (ExtraInfo a) where
163+ mempty = ExtraInfo mempty
164+ ExtraInfo a `mappend` ExtraInfo b =
165+ ExtraInfo $ HM.unionWith combineAction a b
166+
167+-- | Type of GET parameter:
168+--
169+-- - Normal corresponds to @QueryParam@, i.e your usual GET parameter
170+-- - List corresponds to @QueryParams@, i.e GET parameters with multiple values
171+-- - Flag corresponds to @QueryFlag@, i.e a value-less GET parameter
172+data ParamKind = Normal | List | Flag
173+ deriving (Eq, Ord, Show)
174+
175+-- | A type to represent an HTTP response. Has an 'Int' status, a list of
176+-- possible 'MediaType's, and a list of example 'ByteString' response bodies.
177+-- Tweak 'defResponse' using the 'respStatus', 'respTypes' and 'respBody'
178+-- lenses if you want.
179+--
180+-- If you want to respond with a non-empty response body, you'll most likely
181+-- want to write a 'ToSample' instance for the type that'll be represented
182+-- as encoded data in the response.
183+--
184+-- Can be tweaked with three lenses.
185+--
186+-- > λ> defResponse
187+-- > Response {_respStatus = 200, _respTypes = [], _respBody = []}
188+-- > λ> defResponse & respStatus .~ 204 & respBody .~ [("If everything goes well", "{ \"status\": \"ok\" }")]
189+-- > Response {_respStatus = 204, _respTypes = [], _respBody = [("If everything goes well", "{ \"status\": \"ok\" }")]}
190+data Response = Response
191+ { _respStatus :: Int
192+ , _respTypes :: [M.MediaType]
193+ , _respBody :: [(Text, M.MediaType, ByteString)]
194+ } deriving (Eq, Ord, Show)
195+
196+-- | Default response: status code 200, no response body.
197+--
198+-- Can be tweaked with two lenses.
199+--
200+-- > λ> defResponse
201+-- > Response {_respStatus = 200, _respBody = Nothing}
202+-- > λ> defResponse & respStatus .~ 204 & respBody .~ Just "[]"
203+-- > Response {_respStatus = 204, _respBody = Just "[]"}
204+defResponse :: Response
205+defResponse = Response 200 [] []
206+
207+-- | A datatype that represents everything that can happen
208+-- at an endpoint, with its lenses:
209+--
210+-- - List of captures ('captures')
211+-- - List of GET parameters ('params')
212+-- - What the request body should look like, if any is requested ('rqbody')
213+-- - What the response should be if everything goes well ('response')
214+--
215+-- You can tweak an 'Action' (like the default 'defAction') with these lenses
216+-- to transform an action and add some information to it.
217+data Action = Action
218+ { _captures :: [DocCapture] -- type collected + user supplied info
219+ , _headers :: [Text] -- type collected
220+ , _params :: [DocQueryParam] -- type collected + user supplied info
221+ , _notes :: [DocNote] -- user supplied
222+ , _mxParams :: [(String, [DocQueryParam])] -- type collected + user supplied info
223+ , _rqtypes :: [M.MediaType] -- type collected
224+ , _rqbody :: [(M.MediaType, ByteString)] -- user supplied
225+ , _response :: Response -- user supplied
226+ } deriving (Eq, Ord, Show)
227+
228+-- | Combine two Actions, we can't make a monoid as merging Response breaks the
229+-- laws.
230+--
231+-- As such, we invent a non-commutative, left associative operation
232+-- 'combineAction' to mush two together taking the response, body and content
233+-- types from the very left.
234+combineAction :: Action -> Action -> Action
235+Action c h p n m ts body resp `combineAction` Action c' h' p' n' m' _ _ _ =
236+ Action (c <> c') (h <> h') (p <> p') (n <> n') (m <> m') ts body resp
237+
238+-- Default 'Action'. Has no 'captures', no GET 'params', expects
239+-- no request body ('rqbody') and the typical response is 'defResponse'.
240+--
241+-- Tweakable with lenses.
242+--
243+-- > λ> defAction
244+-- > Action {_captures = [], _headers = [], _params = [], _mxParams = [], _rqbody = Nothing, _response = Response {_respStatus = 200, _respBody = Nothing}}
245+-- > λ> defAction & response.respStatus .~ 201
246+-- > Action {_captures = [], _headers = [], _params = [], _mxParams = [], _rqbody = Nothing, _response = Response {_respStatus = 201, _respBody = Nothing}}
247+defAction :: Action
248+defAction =
249+ Action []
250+ []
251+ []
252+ []
253+ []
254+ []
255+ []
256+ defResponse
257+
258+-- | Create an API that's comprised of a single endpoint.
259+-- 'API' is a 'Monoid', so combine multiple endpoints with
260+-- 'mappend' or '<>'.
261+single :: Endpoint -> Action -> API
262+single e a = API mempty (HM.singleton e a)
263+
264+-- gimme some lenses
265+makeLenses ''API
266+makeLenses ''Endpoint
267+makeLenses ''DocCapture
268+makeLenses ''DocQueryParam
269+makeLenses ''DocIntro
270+makeLenses ''DocNote
271+makeLenses ''Response
272+makeLenses ''Action
273+
274+-- | Generate the docs for a given API that implements 'HasDocs'. This is the
275+-- default way to create documentation.
276+docs :: HasDocs (Canonicalize layout) => Proxy layout -> API
277+docs p = docsFor (canonicalize p) (defEndpoint, defAction)
278+
279+-- | Closed type family, check if endpoint is exactly within API.
280+
281+-- We aren't sure what affects how an Endpoint is built up, so we require an
282+-- exact match.
283+type family IsIn (endpoint :: *) (api :: *) :: Constraint where
284+ IsIn e (sa :<|> sb) = Or (IsIn e sa) (IsIn e sb)
285+ IsIn (e :> sa) (e :> sb) = IsIn sa sb
286+ IsIn e e = ()
287+
288+-- | Create an 'ExtraInfo' that is garunteed to be within the given API layout.
289+--
290+-- The safety here is to ensure that you only add custom documentation to an
291+-- endpoint that actually exists within your API.
292+--
293+-- > extra :: ExtraInfo TestApi
294+-- > extra =
295+-- > extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $
296+-- > defAction & headers <>~ ["unicorns"]
297+-- > & notes <>~ [ DocNote "Title" ["This is some text"]
298+-- > , DocNote "Second secton" ["And some more"]
299+-- > ]
300+
301+extraInfo :: (IsIn endpoint layout, HasLink endpoint, HasDocs endpoint)
302+ => Proxy endpoint -> Action -> ExtraInfo layout
303+extraInfo p action =
304+ let api = docsFor p (defEndpoint, defAction)
305+ -- Assume one endpoint, HasLink constraint means that we should only ever
306+ -- point at one endpoint.
307+ in ExtraInfo $ api ^. apiEndpoints & traversed .~ action
308+
309+-- | Generate documentation given some extra introductions (in the form of
310+-- 'DocInfo') and some extra endpoint documentation (in the form of
311+-- 'ExtraInfo'.
312+--
313+-- The extra introductions will be prepended to the top of the documentation,
314+-- before the specific endpoint documentation. The extra endpoint documentation
315+-- will be "unioned" with the automatically generated endpoint documentation.
316+--
317+-- You are expected to build up the ExtraInfo with the Monoid instance and
318+-- 'extraInfo'.
319+--
320+-- If you only want to add an introduction, use 'docsWithIntros'.
321+docsWith :: HasDocs (Canonicalize layout)
322+ => [DocIntro]
323+ -> ExtraInfo layout
324+ -> Proxy layout
325+ -> API
326+docsWith intros (ExtraInfo endpoints) p =
327+ docs p & apiIntros <>~ intros
328+ & apiEndpoints %~ HM.unionWith combineAction endpoints
329+
330+
331+-- | Generate the docs for a given API that implements 'HasDocs' with with any
332+-- number of introduction(s)
333+docsWithIntros :: HasDocs (Canonicalize layout) => [DocIntro] -> Proxy layout -> API
334+docsWithIntros intros = docsWith intros mempty
335+
336+-- | The class that abstracts away the impact of API combinators
337+-- on documentation generation.
338+class HasDocs layout where
339+ docsFor :: Proxy layout -> (Endpoint, Action) -> API
340+
341+-- | The class that lets us display a sample input or output in the supported
342+-- content-types when generating documentation for endpoints that either:
343+--
344+-- - expect a request body, or
345+-- - return a non empty response body
346+--
347+-- Example of an instance:
348+--
349+-- > {-# LANGUAGE DeriveGeneric #-}
350+-- > {-# LANGUAGE OverloadedStrings #-}
351+-- >
352+-- > import Data.Aeson
353+-- > import Data.Text
354+-- > import GHC.Generics
355+-- >
356+-- > data Greet = Greet { _msg :: Text }
357+-- > deriving (Generic, Show)
358+-- >
359+-- > instance FromJSON Greet
360+-- > instance ToJSON Greet
361+-- >
362+-- > instance ToSample Greet where
363+-- > toSample = Just g
364+-- >
365+-- > where g = Greet "Hello, haskeller!"
366+--
367+-- You can also instantiate this class using 'toSamples' instead of
368+-- 'toSample': it lets you specify different responses along with
369+-- some context (as 'Text') that explains when you're supposed to
370+-- get the corresponding response.
371+class ToSample a where
372+ {-# MINIMAL (toSample | toSamples) #-}
373+ toSample :: Maybe a
374+ toSample = snd <$> listToMaybe samples
375+ where samples = toSamples :: [(Text, a)]
376+
377+ toSamples :: [(Text, a)]
378+ toSamples = maybe [] (return . ("",)) s
379+ where s = toSample :: Maybe a
380+
381+-- | Synthesise a sample value of a type, encoded in the specified media types.
382+sampleByteString
383+ :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a)
384+ => Proxy ctypes
385+ -> Proxy a
386+ -> [(M.MediaType, ByteString)]
387+sampleByteString ctypes@Proxy Proxy =
388+ maybe [] (allMimeRender ctypes) (toSample :: Maybe a)
389+
390+-- | Synthesise a list of sample values of a particular type, encoded in the
391+-- specified media types.
392+sampleByteStrings
393+ :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a)
394+ => Proxy ctypes
395+ -> Proxy a
396+ -> [(Text, M.MediaType, ByteString)]
397+sampleByteStrings ctypes@Proxy Proxy =
398+ let samples = toSamples :: [(Text, a)]
399+ enc (t, s) = uncurry (t,,) <$> allMimeRender ctypes s
400+ in concatMap enc samples
401+
402+-- | Generate a list of 'MediaType' values describing the content types
403+-- accepted by an API component.
404+class SupportedTypes (list :: [*]) where
405+ supportedTypes :: Proxy list -> [M.MediaType]
406+
407+instance SupportedTypes '[] where
408+ supportedTypes Proxy = []
409+
410+instance (Accept ctype, SupportedTypes rest) => SupportedTypes (ctype ': rest)
411+ where
412+ supportedTypes Proxy =
413+ contentType (Proxy :: Proxy ctype) : supportedTypes (Proxy :: Proxy rest)
414+
415+-- | The class that helps us automatically get documentation
416+-- for GET parameters.
417+--
418+-- Example of an instance:
419+--
420+-- > instance ToParam (QueryParam "capital" Bool) where
421+-- > toParam _ =
422+-- > DocQueryParam "capital"
423+-- > ["true", "false"]
424+-- > "Get the greeting message in uppercase (true) or not (false). Default is false."
425+class ToParam t where
426+ toParam :: Proxy t -> DocQueryParam
427+
428+-- | The class that helps us automatically get documentation
429+-- for URL captures.
430+--
431+-- Example of an instance:
432+--
433+-- > instance ToCapture (Capture "name" Text) where
434+-- > toCapture _ = DocCapture "name" "name of the person to greet"
435+class ToCapture c where
436+ toCapture :: Proxy c -> DocCapture
437+
438+-- | Generate documentation in Markdown format for
439+-- the given 'API'.
440+markdown :: API -> String
441+markdown api = unlines $
442+ introsStr (api ^. apiIntros)
443+ ++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints)
444+
445+ where printEndpoint :: Endpoint -> Action -> [String]
446+ printEndpoint endpoint action =
447+ str :
448+ "" :
449+ notesStr (action ^. notes) ++
450+ capturesStr (action ^. captures) ++
451+ mxParamsStr (action ^. mxParams) ++
452+ headersStr (action ^. headers) ++
453+ paramsStr (action ^. params) ++
454+ rqbodyStr (action ^. rqtypes) (action ^. rqbody) ++
455+ responseStr (action ^. response) ++
456+ []
457+
458+ where str = "## " ++ show (endpoint^.method)
459+ ++ " " ++ showPath (endpoint^.path)
460+
461+ introsStr :: [DocIntro] -> [String]
462+ introsStr = concatMap introStr
463+
464+ introStr :: DocIntro -> [String]
465+ introStr i =
466+ ("#### " ++ i ^. introTitle) :
467+ "" :
468+ intersperse "" (i ^. introBody) ++
469+ "" :
470+ []
471+
472+ notesStr :: [DocNote] -> [String]
473+ notesStr = concatMap noteStr
474+
475+ noteStr :: DocNote -> [String]
476+ noteStr nt =
477+ ("#### " ++ nt ^. noteTitle) :
478+ "" :
479+ intersperse "" (nt ^. noteBody) ++
480+ "" :
481+ []
482+
483+ capturesStr :: [DocCapture] -> [String]
484+ capturesStr [] = []
485+ capturesStr l =
486+ "#### Captures:" :
487+ "" :
488+ map captureStr l ++
489+ "" :
490+ []
491+
492+ captureStr cap =
493+ "- *" ++ (cap ^. capSymbol) ++ "*: " ++ (cap ^. capDesc)
494+
495+ mxParamsStr :: [(String, [DocQueryParam])] -> [String]
496+ mxParamsStr [] = []
497+ mxParamsStr l =
498+ "#### Matrix Parameters:" :
499+ "" :
500+ map segmentStr l
501+ segmentStr :: (String, [DocQueryParam]) -> String
502+ segmentStr (segment, l) = unlines $
503+ ("**" ++ segment ++ "**:") :
504+ "" :
505+ map paramStr l ++
506+ "" :
507+ []
508+
509+ headersStr :: [Text] -> [String]
510+ headersStr [] = []
511+ headersStr l = [""] ++ map headerStr l ++ [""]
512+
513+ where headerStr hname = "- This endpoint is sensitive to the value of the **"
514+ ++ unpack hname ++ "** HTTP header."
515+
516+ paramsStr :: [DocQueryParam] -> [String]
517+ paramsStr [] = []
518+ paramsStr l =
519+ "#### GET Parameters:" :
520+ "" :
521+ map paramStr l ++
522+ "" :
523+ []
524+
525+ paramStr param = unlines $
526+ ("- " ++ param ^. paramName) :
527+ (if (not (null values) || param ^. paramKind /= Flag)
528+ then [" - **Values**: *" ++ intercalate ", " values ++ "*"]
529+ else []) ++
530+ (" - **Description**: " ++ param ^. paramDesc) :
531+ (if (param ^. paramKind == List)
532+ then [" - This parameter is a **list**. All GET parameters with the name "
533+ ++ param ^. paramName ++ "[] will forward their values in a list to the handler."]
534+ else []) ++
535+ (if (param ^. paramKind == Flag)
536+ then [" - This parameter is a **flag**. This means no value is expected to be associated to this parameter."]
537+ else []) ++
538+ []
539+
540+ where values = param ^. paramValues
541+
542+ rqbodyStr :: [M.MediaType] -> [(M.MediaType, ByteString)]-> [String]
543+ rqbodyStr [] [] = []
544+ rqbodyStr types samples =
545+ ["#### Request:", ""]
546+ <> formatTypes types
547+ <> concatMap formatBody samples
548+
549+ formatTypes [] = []
550+ formatTypes ts = ["- Supported content types are:", ""]
551+ <> map (\t -> " - `" <> show t <> "`") ts
552+ <> [""]
553+
554+ formatBody (m, b) =
555+ "- Example: `" <> cs (show m) <> "`" :
556+ contentStr m b
557+
558+ markdownForType mime_type =
559+ case (M.mainType mime_type, M.subType mime_type) of
560+ ("text", "html") -> "html"
561+ ("application", "xml") -> "xml"
562+ ("application", "json") -> "javascript"
563+ ("application", "javascript") -> "javascript"
564+ ("text", "css") -> "css"
565+ (_, _) -> ""
566+
567+ contentStr mime_type body =
568+ "" :
569+ "```" <> markdownForType mime_type :
570+ cs body :
571+ "```" :
572+ "" :
573+ []
574+
575+ responseStr :: Response -> [String]
576+ responseStr resp =
577+ "#### Response:" :
578+ "" :
579+ ("- Status code " ++ show (resp ^. respStatus)) :
580+ "" :
581+ formatTypes (resp ^. respTypes) ++
582+ bodies
583+
584+ where bodies = case resp ^. respBody of
585+ [] -> ["- No response body\n"]
586+ [("", t, r)] -> "- Response body as below." : contentStr t r
587+ xs ->
588+ concatMap (\(ctx, t, r) -> ("- " <> T.unpack ctx) : contentStr t r) xs
589+
590+-- * Instances
591+
592+-- | The generated docs for @a ':<|>' b@ just appends the docs
593+-- for @a@ with the docs for @b@.
594+instance (HasDocs layout1, HasDocs layout2)
595+ => HasDocs (layout1 :<|> layout2) where
596+
597+ docsFor Proxy (ep, action) = docsFor p1 (ep, action) <> docsFor p2 (ep, action)
598+
599+ where p1 :: Proxy layout1
600+ p1 = Proxy
601+
602+ p2 :: Proxy layout2
603+ p2 = Proxy
604+
605+-- | @"books" :> 'Capture' "isbn" Text@ will appear as
606+-- @/books/:isbn@ in the docs.
607+instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs sublayout)
608+ => HasDocs (Capture sym a :> sublayout) where
609+
610+ docsFor Proxy (endpoint, action) =
611+ docsFor sublayoutP (endpoint', action')
612+
613+ where sublayoutP = Proxy :: Proxy sublayout
614+ captureP = Proxy :: Proxy (Capture sym a)
615+
616+ action' = over captures (|> toCapture captureP) action
617+ endpoint' = over path (\p -> p ++ [":" ++ symbolVal symP]) endpoint
618+ symP = Proxy :: Proxy sym
619+
620+
621+instance HasDocs Delete where
622+ docsFor Proxy (endpoint, action) =
623+ single endpoint' action'
624+
625+ where endpoint' = endpoint & method .~ DocDELETE
626+
627+ action' = action & response.respBody .~ []
628+ & response.respStatus .~ 204
629+
630+instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
631+ => HasDocs (Get cts a) where
632+ docsFor Proxy (endpoint, action) =
633+ single endpoint' action'
634+
635+ where endpoint' = endpoint & method .~ DocGET
636+ action' = action & response.respBody .~ sampleByteStrings t p
637+ & response.respTypes .~ supportedTypes t
638+ t = Proxy :: Proxy cts
639+ p = Proxy :: Proxy a
640+
641+instance (KnownSymbol sym, HasDocs sublayout)
642+ => HasDocs (Header sym a :> sublayout) where
643+ docsFor Proxy (endpoint, action) =
644+ docsFor sublayoutP (endpoint, action')
645+
646+ where sublayoutP = Proxy :: Proxy sublayout
647+ action' = over headers (|> headername) action
648+ headername = pack $ symbolVal (Proxy :: Proxy sym)
649+
650+instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
651+ => HasDocs (Post cts a) where
652+ docsFor Proxy (endpoint, action) =
653+ single endpoint' action'
654+
655+ where endpoint' = endpoint & method .~ DocPOST
656+ action' = action & response.respBody .~ sampleByteStrings t p
657+ & response.respTypes .~ supportedTypes t
658+ & response.respStatus .~ 201
659+ t = Proxy :: Proxy cts
660+ p = Proxy :: Proxy a
661+
662+instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
663+ => HasDocs (Put cts a) where
664+ docsFor Proxy (endpoint, action) =
665+ single endpoint' action'
666+
667+ where endpoint' = endpoint & method .~ DocPUT
668+ action' = action & response.respBody .~ sampleByteStrings t p
669+ & response.respTypes .~ supportedTypes t
670+ & response.respStatus .~ 200
671+ t = Proxy :: Proxy cts
672+ p = Proxy :: Proxy a
673+
674+instance (KnownSymbol sym, ToParam (QueryParam sym a), HasDocs sublayout)
675+ => HasDocs (QueryParam sym a :> sublayout) where
676+
677+ docsFor Proxy (endpoint, action) =
678+ docsFor sublayoutP (endpoint, action')
679+
680+ where sublayoutP = Proxy :: Proxy sublayout
681+ paramP = Proxy :: Proxy (QueryParam sym a)
682+ action' = over params (|> toParam paramP) action
683+
684+instance (KnownSymbol sym, ToParam (QueryParams sym a), HasDocs sublayout)
685+ => HasDocs (QueryParams sym a :> sublayout) where
686+
687+ docsFor Proxy (endpoint, action) =
688+ docsFor sublayoutP (endpoint, action')
689+
690+ where sublayoutP = Proxy :: Proxy sublayout
691+ paramP = Proxy :: Proxy (QueryParams sym a)
692+ action' = over params (|> toParam paramP) action
693+
694+
695+instance (KnownSymbol sym, ToParam (QueryFlag sym), HasDocs sublayout)
696+ => HasDocs (QueryFlag sym :> sublayout) where
697+
698+ docsFor Proxy (endpoint, action) =
699+ docsFor sublayoutP (endpoint, action')
700+
701+ where sublayoutP = Proxy :: Proxy sublayout
702+ paramP = Proxy :: Proxy (QueryFlag sym)
703+ action' = over params (|> toParam paramP) action
704+
705+
706+instance (KnownSymbol sym, ToParam (MatrixParam sym a), HasDocs sublayout)
707+ => HasDocs (MatrixParam sym a :> sublayout) where
708+
709+ docsFor Proxy (endpoint, action) =
710+ docsFor sublayoutP (endpoint', action')
711+
712+ where sublayoutP = Proxy :: Proxy sublayout
713+ paramP = Proxy :: Proxy (MatrixParam sym a)
714+ segment = endpoint ^. (path._last)
715+ segment' = action ^. (mxParams._last._1)
716+ endpoint' = over (path._last) (\p -> p ++ ";" ++ symbolVal symP ++ "=<value>") endpoint
717+
718+ action' = if segment' /= segment
719+ -- This is the first matrix parameter for this segment, insert a new entry into the mxParams list
720+ then over mxParams (|> (segment, [toParam paramP])) action
721+ -- We've already inserted a matrix parameter for this segment, append to the existing list
722+ else action & mxParams._last._2 <>~ [toParam paramP]
723+ symP = Proxy :: Proxy sym
724+
725+
726+instance (KnownSymbol sym, {- ToParam (MatrixParams sym a), -} HasDocs sublayout)
727+ => HasDocs (MatrixParams sym a :> sublayout) where
728+
729+ docsFor Proxy (endpoint, action) =
730+ docsFor sublayoutP (endpoint', action)
731+
732+ where sublayoutP = Proxy :: Proxy sublayout
733+ endpoint' = over path (\p -> p ++ [";" ++ symbolVal symP ++ "=<value>"]) endpoint
734+ symP = Proxy :: Proxy sym
735+
736+
737+instance (KnownSymbol sym, {- ToParam (MatrixFlag sym), -} HasDocs sublayout)
738+ => HasDocs (MatrixFlag sym :> sublayout) where
739+
740+ docsFor Proxy (endpoint, action) =
741+ docsFor sublayoutP (endpoint', action)
742+
743+ where sublayoutP = Proxy :: Proxy sublayout
744+
745+ endpoint' = over path (\p -> p ++ [";" ++ symbolVal symP]) endpoint
746+ symP = Proxy :: Proxy sym
747+
748+instance HasDocs Raw where
749+ docsFor _proxy (endpoint, action) =
750+ single endpoint action
751+
752+-- TODO: We use 'AllMimeRender' here because we need to be able to show the
753+-- example data. However, there's no reason to believe that the instances of
754+-- 'AllMimeUnrender' and 'AllMimeRender' actually agree (or to suppose that
755+-- both are even defined) for any particular type.
756+instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, HasDocs sublayout, SupportedTypes cts)
757+ => HasDocs (ReqBody cts a :> sublayout) where
758+
759+ docsFor Proxy (endpoint, action) =
760+ docsFor sublayoutP (endpoint, action')
761+
762+ where sublayoutP = Proxy :: Proxy sublayout
763+ action' = action & rqbody .~ sampleByteString t p
764+ & rqtypes .~ supportedTypes t
765+ t = Proxy :: Proxy cts
766+ p = Proxy :: Proxy a
767+
768+instance (KnownSymbol path, HasDocs sublayout) => HasDocs (path :> sublayout) where
769+
770+ docsFor Proxy (endpoint, action) =
771+ docsFor sublayoutP (endpoint', action)
772+
773+ where sublayoutP = Proxy :: Proxy sublayout
774+ endpoint' = endpoint & path <>~ [symbolVal pa]
775+ pa = Proxy :: Proxy path
776+
777+{-
778+
779+-- | Serve your API's docs as markdown embedded in an html \<pre> tag.
780+--
781+-- > type MyApi = "users" :> Get [User]
782+-- > :<|> "docs :> Raw
783+-- >
784+-- > apiProxy :: Proxy MyApi
785+-- > apiProxy = Proxy
786+-- >
787+-- > server :: Server MyApi
788+-- > server = listUsers
789+-- > :<|> serveDocumentation apiProxy
790+serveDocumentation :: HasDocs api => Proxy api -> Server Raw
791+serveDocumentation proxy _request respond =
792+ respond $ responseLBS ok200 [] $ cs $ toHtml $ markdown $ docs proxy
793+
794+toHtml :: String -> String
795+toHtml md =
796+ "<html>" ++
797+ "<body>" ++
798+ "<pre>" ++
799+ md ++
800+ "</pre>" ++
801+ "</body>" ++
802+ "</html>"
803+-}
servant-docs/test/Servant/DocsSpec.hsnew file+64
@@ -0,0 +1,64 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE MultiParamTypeClasses #-}
5+{-# LANGUAGE TypeOperators #-}
6+{-# LANGUAGE TypeSynonymInstances #-}
7+{-# OPTIONS_GHC -fno-warn-orphans #-}
8+module Servant.DocsSpec where
9+
10+import Data.Aeson
11+import Data.Proxy
12+import Data.String.Conversions (cs)
13+import GHC.Generics
14+import Test.Hspec
15+
16+import Servant.API
17+import Servant.Docs.Internal
18+
19+spec :: Spec
20+spec = describe "Servant.Docs" $ do
21+
22+ describe "markdown" $ do
23+ let md = markdown (docs (Proxy :: Proxy TestApi1))
24+
25+ it "mentions supported content-types" $ do
26+ md `shouldContain` "application/json"
27+ md `shouldContain` "text/plain;charset=utf-8"
28+
29+ it "mentions status codes" $ do
30+ md `shouldContain` "Status code 200"
31+ md `shouldContain` "Status code 201"
32+
33+ it "mentions methods" $ do
34+ md `shouldContain` "POST"
35+ md `shouldContain` "GET"
36+
37+ it "contains response samples" $ do
38+ md `shouldContain` "{\"dt1field1\":\"field 1\",\"dt1field2\":13}"
39+ it "contains request body samples" $ do
40+ md `shouldContain` "17"
41+-- * APIs
42+
43+data Datatype1 = Datatype1 { dt1field1 :: String
44+ , dt1field2 :: Int
45+ } deriving (Eq, Show, Generic)
46+
47+instance ToJSON Datatype1
48+
49+instance ToSample Datatype1 where
50+ toSample = Just $ Datatype1 "field 1" 13
51+
52+instance ToSample String where
53+ toSample = Just "a string"
54+
55+instance ToSample Int where
56+ toSample = Just 17
57+
58+instance MimeRender PlainText Int where
59+ mimeRender _ = cs . show
60+
61+
62+type TestApi1 = Get '[JSON, PlainText] Int
63+ :<|> ReqBody '[JSON] String :> Post '[JSON] Datatype1
64+
servant-docs/test/Spec.hsnew file+1
@@ -0,0 +1 @@
1+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
servant-jquery/CHANGELOG.mdnew file+12
@@ -0,0 +1,12 @@
1+0.3
2+---
3+* Extend `HeaderArg` to support more advanced HTTP header handling (https://github.com/haskell-servant/servant-jquery/pull/6)
4+* Support content-type aware combinators (but require that endpoints support JSON)
5+* Add support for Matrix params (https://github.com/haskell-servant/servant-jquery/pull/11)
6+* Add functions that directly generate the Javascript code from the API type without having to manually pattern match on the result.
7+
8+0.2.2
9+-----
10+
11+* Fix an issue where toplevel Raw endpoints would generate a JS function with no name (https://github.com/haskell-servant/servant-jquery/issues/2)
12+* Replace dots by _ in paths (https://github.com/haskell-servant/servant-jquery/issues/1)
servant-jquery/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2014, Zalora South East Asia Pte Ltd
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Zalora South East Asia Pte Ltd nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-jquery/README.mdnew file+97
@@ -0,0 +1,97 @@
1+# servant-jquery
2+
3+[![Build Status](https://secure.travis-ci.org/haskell-servant/servant-jquery.svg)](http://travis-ci.org/haskell-servant/servant-jquery)
4+[![Coverage Status](https://coveralls.io/repos/haskell-servant/servant-jquery/badge.svg)](https://coveralls.io/r/haskell-servant/servant-jquery)
5+
6+![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
7+
8+This library lets you derive automatically (JQuery based) Javascript functions that let you query each endpoint of a *servant* webservice.
9+
10+## Example
11+
12+Read more about the following example [here](https://github.com/haskell-servant/servant-jquery/tree/master/examples#examples).
13+
14+``` haskell
15+{-# LANGUAGE DataKinds #-}
16+{-# LANGUAGE TypeOperators #-}
17+{-# LANGUAGE DeriveGeneric #-}
18+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
19+
20+import Control.Concurrent.STM
21+import Control.Monad.IO.Class
22+import Data.Aeson
23+import Data.Proxy
24+import GHC.Generics
25+import Network.Wai.Handler.Warp (run)
26+import Servant
27+import Servant.JQuery
28+import System.FilePath
29+
30+-- * A simple Counter data type
31+newtype Counter = Counter { value :: Int }
32+ deriving (Generic, Show, Num)
33+
34+instance ToJSON Counter
35+
36+-- * Shared counter operations
37+
38+-- Creating a counter that starts from 0
39+newCounter :: IO (TVar Counter)
40+newCounter = newTVarIO 0
41+
42+-- Increasing the counter by 1
43+counterPlusOne :: MonadIO m => TVar Counter -> m Counter
44+counterPlusOne counter = liftIO . atomically $ do
45+ oldValue <- readTVar counter
46+ let newValue = oldValue + 1
47+ writeTVar counter newValue
48+ return newValue
49+
50+currentValue :: MonadIO m => TVar Counter -> m Counter
51+currentValue counter = liftIO $ readTVarIO counter
52+
53+-- * Our API type
54+type TestApi = "counter" :> Post Counter -- endpoint for increasing the counter
55+ :<|> "counter" :> Get Counter -- endpoint to get the current value
56+ :<|> Raw -- used for serving static files
57+
58+testApi :: Proxy TestApi
59+testApi = Proxy
60+
61+-- * Server-side handler
62+
63+-- where our static files reside
64+www :: FilePath
65+www = "examples/www"
66+
67+-- defining handlers
68+server :: TVar Counter -> Server TestApi
69+server counter = counterPlusOne counter -- (+1) on the TVar
70+ :<|> currentValue counter -- read the TVar
71+ :<|> serveDirectory www -- serve static files
72+
73+runServer :: TVar Counter -- ^ shared variable for the counter
74+ -> Int -- ^ port the server should listen on
75+ -> IO ()
76+runServer var port = run port (serve testApi $ server var)
77+
78+-- * Generating the JQuery code
79+
80+incCounterJS :<|> currentValueJS :<|> _ = jquery testApi
81+
82+writeJS :: FilePath -> [AjaxReq] -> IO ()
83+writeJS fp functions = writeFile fp $
84+ concatMap generateJS functions
85+
86+main :: IO ()
87+main = do
88+ -- write the JS code to www/api.js at startup
89+ writeJS (www </> "api.js")
90+ [ incCounterJS, currentValueJS ]
91+
92+ -- setup a shared counter
93+ cnt <- newCounter
94+
95+ -- listen to requests on port 8080
96+ runServer cnt 8080
97+```
servant-jquery/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
servant-jquery/TODO.mdnew file+1
@@ -0,0 +1 @@
1+- Investigate the best way to offer cross-origin requests
servant-jquery/docs.shnew file+52
@@ -0,0 +1,52 @@
1+SERVANT_DIR=/tmp/servant-jquery-gh-pages
2+
3+# Make a temporary clone
4+
5+rm -rf $SERVANT_DIR
6+
7+git clone . $SERVANT_DIR
8+
9+cd $SERVANT_DIR
10+
11+# Make sure to pull the latest
12+
13+git remote add haskell-servant git@github.com:haskell-servant/servant-jquery.git
14+
15+git fetch haskell-servant
16+
17+git reset --hard haskell-servant/gh-pages
18+
19+# Clear everything away
20+
21+git rm -rf $SERVANT_DIR/*
22+
23+# Switch back and build the haddocks
24+
25+cd -
26+
27+cabal configure --builddir=$SERVANT_DIR
28+
29+cabal haddock --hoogle --hyperlink-source --html-location='https://hackage.haskell.org/package/$pkg-$version/docs' --builddir=$SERVANT_DIR
30+
31+commit_hash=$(git rev-parse HEAD)
32+
33+# Move the HTML docs to the root
34+
35+cd $SERVANT_DIR
36+
37+rm *
38+rm -rf build
39+mv doc/html/servant-jquery/* .
40+rm -r doc/
41+
42+# Add everything
43+
44+git add .
45+
46+git commit -m "Built from $commit_hash"
47+
48+# Push to update the pages
49+
50+git push haskell-servant HEAD:gh-pages
51+
52+rm -rf $SERVANT_DIR
servant-jquery/examples/README.mdnew file+17
@@ -0,0 +1,17 @@
1+# Examples
2+
3+## counter
4+
5+This example demonstrates a *servant* server that holds a shared variable (using a `TVar`) and exposes an endpoint for reading its current value and another one for increasing its current value by 1.
6+
7+In addition to that, it shows how you can generate the jquery-powered javascript functions corresponding to each endpoint, i.e one for reading the current value and one for increasing the value, and integrates all of that in a very simple HTML page. All these static files are served using the `serveDirectory` function from *servant*.
8+
9+To see this all in action, simply run:
10+
11+``` bash
12+$ cabal run counter
13+```
14+
15+And point your browser to [http://localhost:8080/index.html](http://localhost:8080/index.html).
16+
17+Copies of the generated javascript functions and of the generated docs are included in `www/api.js` and `counter.md` respectively.
servant-jquery/examples/counter.hsnew file+82
@@ -0,0 +1,82 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE TypeOperators #-}
3+{-# LANGUAGE DeriveGeneric #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+
6+import Control.Concurrent.STM
7+import Control.Monad.IO.Class
8+import Data.Aeson
9+import Data.Proxy
10+import GHC.Generics
11+import Network.Wai.Handler.Warp (run)
12+import Servant
13+import Servant.JQuery
14+import System.FilePath
15+
16+-- * A simple Counter data type
17+newtype Counter = Counter { value :: Int }
18+ deriving (Generic, Show, Num)
19+
20+instance ToJSON Counter
21+
22+-- * Shared counter operations
23+
24+-- Creating a counter that starts from 0
25+newCounter :: IO (TVar Counter)
26+newCounter = newTVarIO 0
27+
28+-- Increasing the counter by 1
29+counterPlusOne :: MonadIO m => TVar Counter -> m Counter
30+counterPlusOne counter = liftIO . atomically $ do
31+ oldValue <- readTVar counter
32+ let newValue = oldValue + 1
33+ writeTVar counter newValue
34+ return newValue
35+
36+currentValue :: MonadIO m => TVar Counter -> m Counter
37+currentValue counter = liftIO $ readTVarIO counter
38+
39+-- * Our API type
40+type TestApi = "counter" :> Post Counter -- endpoint for increasing the counter
41+ :<|> "counter" :> Get Counter -- endpoint to get the current value
42+ :<|> Raw -- used for serving static files
43+
44+testApi :: Proxy TestApi
45+testApi = Proxy
46+
47+-- * Server-side handler
48+
49+-- where our static files reside
50+www :: FilePath
51+www = "examples/www"
52+
53+-- defining handlers
54+server :: TVar Counter -> Server TestApi
55+server counter = counterPlusOne counter -- (+1) on the TVar
56+ :<|> currentValue counter -- read the TVar
57+ :<|> serveDirectory www -- serve static files
58+
59+runServer :: TVar Counter -- ^ shared variable for the counter
60+ -> Int -- ^ port the server should listen on
61+ -> IO ()
62+runServer var port = run port (serve testApi $ server var)
63+
64+-- * Generating the JQuery code
65+
66+incCounterJS :<|> currentValueJS :<|> _ = jquery testApi
67+
68+writeJS :: FilePath -> [AjaxReq] -> IO ()
69+writeJS fp functions = writeFile fp $
70+ concatMap generateJS functions
71+
72+main :: IO ()
73+main = do
74+ -- write the JS code to www/api.js at startup
75+ writeJS (www </> "api.js")
76+ [ incCounterJS, currentValueJS ]
77+
78+ -- setup a shared counter
79+ cnt <- newCounter
80+
81+ -- listen to requests on port 8080
82+ runServer cnt 8080
servant-jquery/examples/counter.mdnew file+39
@@ -0,0 +1,39 @@
1+POST /counter
2+-------------
3+
4+**Response**:
5+
6+ - Status code 201
7+ - Response body as below.
8+
9+``` javascript
10+{"value":0}
11+```
12+
13+GET /doc
14+--------
15+
16+**Response**:
17+
18+ - Status code 200
19+ - No response body
20+
21+GET /counter
22+------------
23+
24+**Response**:
25+
26+ - Status code 200
27+ - Response body as below.
28+
29+``` javascript
30+{"value":0}
31+```
32+
33+GET /
34+-----
35+
36+**Response**:
37+
38+ - Status code 200
39+ - No response body
servant-jquery/examples/www/api.jsnew file+20
@@ -0,0 +1,20 @@
1+
2+function postcounter(onSuccess, onError)
3+{
4+ $.ajax(
5+ { url: '/counter'
6+ , success: onSuccess
7+ , error: onError
8+ , type: 'POST'
9+ });
10+}
11+
12+function getcounter(onSuccess, onError)
13+{
14+ $.ajax(
15+ { url: '/counter'
16+ , success: onSuccess
17+ , error: onError
18+ , type: 'GET'
19+ });
20+}
servant-jquery/examples/www/index.htmlnew file+40
@@ -0,0 +1,40 @@
1+<html>
2+<head>
3+ <title>Servant: counter</title>
4+ <style>
5+ body { text-align: center; }
6+ #counter { color: green; }
7+ #inc { margin: 0px 20px; background-color: green; color: white; }
8+ </style>
9+</head>
10+<body>
11+<span id="counter">Counter: 0</span>
12+<button id="inc">Increase</button>
13+or <a href="/doc">view the docs</a>
14+
15+<script src="/jquery.min.js" type="text/javascript"></script>
16+<script src="/api.js" type="text/javascript"></script>
17+<script type="text/javascript">
18+$(document).ready(function() {
19+ // we get the current value stored by the server when the page is loaded
20+ getcounter(updateCounter, alert);
21+
22+ // we update the value every 1sec, in the same way
23+ window.setInterval(function() {
24+ getcounter(updateCounter, alert);
25+ }, 1000);
26+});
27+
28+function updateCounter(response)
29+{
30+ $('#counter').html('Counter: ' + response.value);
31+}
32+
33+// when the button is clicked, ask the server to increase
34+// the value by one
35+$('#inc').click(function() {
36+ postcounter(updateCounter, alert);
37+});
38+</script>
39+</body>
40+</html>
servant-jquery/examples/www/jquery.min.jsnew file+4
@@ -0,0 +1,4 @@
1+/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
2+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;
3+if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==cb()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===cb()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ab:bb):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:bb,isPropagationStopped:bb,isImmediatePropagationStopped:bb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ab,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ab,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ab,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=bb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=bb),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function db(a){var b=eb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var eb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fb=/ jQuery\d+="(?:null|\d+)"/g,gb=new RegExp("<(?:"+eb+")[\\s/>]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/<tbody/i,lb=/<|&#?\w+;/,mb=/<(?:script|style|link)/i,nb=/checked\s*(?:[^=]|=\s*.checked.)/i,ob=/^$|\/(?:java|ecma)script/i,pb=/^true\/(.*)/,qb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,rb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?"<table>"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e)}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")
4+},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$b=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_b||(_b=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_b),_b=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.ActiveXObject&&m(a).on("unload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
servant-jquery/servant-jquery.cabalnew file+78
@@ -0,0 +1,78 @@
1+name: servant-jquery
2+version: 0.2.2
3+synopsis: Automatically derive (jquery) javascript functions to query servant webservices
4+description:
5+ Automatically derive jquery-based javascript functions to query servant webservices.
6+ .
7+ Example <https://github.com/haskell-servant/servant-jquery/blob/master/examples/counter.hs here> that serves the generated javascript to a webpage that lets you
8+ trigger webservice calls.
9+license: BSD3
10+license-file: LICENSE
11+author: Alp Mestanogullari
12+maintainer: alpmestan@gmail.com
13+copyright: 2014 Alp Mestanogullari
14+category: Web
15+build-type: Simple
16+cabal-version: >=1.10
17+homepage: http://haskell-servant.github.io/
18+Bug-reports: http://github.com/haskell-servant/servant-jquery/issues
19+extra-source-files:
20+ CHANGELOG.md
21+ README.md
22+source-repository head
23+ type: git
24+ location: http://github.com/haskell-servant/servant-jquery.git
25+
26+flag example
27+ description: Build the example too
28+ manual: True
29+ default: False
30+
31+library
32+ exposed-modules: Servant.JQuery
33+ other-modules: Servant.JQuery.Internal
34+ build-depends: base >=4.5 && <5
35+ , charset
36+ , lens >= 4
37+ , servant >= 0.2.2
38+ , text
39+ hs-source-dirs: src
40+ default-language: Haskell2010
41+ ghc-options: -Wall
42+
43+executable counter
44+ main-is: counter.hs
45+ ghc-options: -O2 -Wall
46+ hs-source-dirs: examples
47+
48+ if flag(example)
49+ buildable: True
50+ else
51+ buildable: False
52+
53+ build-depends:
54+ aeson
55+ , base
56+ , filepath
57+ , servant >= 0.2.2
58+ , servant-server >= 0.2.3
59+ , servant-jquery >= 0.2.2
60+ , stm
61+ , transformers
62+ , warp
63+ default-language: Haskell2010
64+
65+test-suite spec
66+ type: exitcode-stdio-1.0
67+ hs-source-dirs: test
68+ ghc-options: -Wall
69+ main-is: Spec.hs
70+ build-depends:
71+ base == 4.*
72+ , lens
73+ , servant-jquery
74+ , servant
75+ , hspec >= 2.0
76+ , hspec-expectations
77+ , language-ecmascript >= 0.16
78+ default-language: Haskell2010
servant-jquery/src/Servant/JQuery.hsnew file+117
@@ -0,0 +1,117 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE TypeOperators #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+-----------------------------------------------------------------------------
6+-- |
7+-- Module : Servant.JQuery
8+-- Copyright : (C) 2014 Alp Mestanogullari
9+-- License : BSD3
10+-- Maintainer : Alp Mestanogullari <alpmestan@gmail.com>
11+-- Stability : experimental
12+-- Portability : non-portable
13+module Servant.JQuery
14+ ( jquery
15+ , generateJS
16+ , jsForAPI
17+ , printJS
18+ , module Servant.JQuery.Internal
19+ , GenerateCode(..)
20+ ) where
21+
22+import Control.Lens
23+import Data.List
24+import Data.Monoid
25+import Data.Proxy
26+import Servant.API
27+import Servant.JQuery.Internal
28+
29+jquery :: HasJQ (Canonicalize layout) => Proxy layout -> JQ layout
30+jquery p = jqueryFor (canonicalize p) defReq
31+
32+-- js codegen
33+generateJS :: AjaxReq -> String
34+generateJS req = "\n" <>
35+ "function " <> fname <> "(" <> argsStr <> ")\n"
36+ <> "{\n"
37+ <> " $.ajax(\n"
38+ <> " { url: " <> url <> "\n"
39+ <> " , success: onSuccess\n"
40+ <> dataBody
41+ <> reqheaders
42+ <> " , error: onError\n"
43+ <> " , type: '" <> method <> "'\n"
44+ <> " });\n"
45+ <> "}\n"
46+
47+ where argsStr = intercalate ", " args
48+ args = captures
49+ ++ map (view argName) queryparams
50+ ++ body
51+ ++ map (toValidFunctionName . (<>) "header" . headerArgName) hs
52+ ++ ["onSuccess", "onError"]
53+
54+ captures = map captureArg
55+ . filter isCapture
56+ $ req ^. reqUrl.path
57+
58+ hs = req ^. reqHeaders
59+
60+ queryparams = req ^.. reqUrl.queryStr.traverse
61+
62+ body = if req ^. reqBody
63+ then ["body"]
64+ else []
65+
66+ dataBody =
67+ if req ^. reqBody
68+ then "\n , data: JSON.stringify(body)\n"
69+ else ""
70+
71+ reqheaders =
72+ if null hs
73+ then ""
74+ else "\n , headers: { " ++ headersStr ++ " }\n"
75+
76+ where headersStr = intercalate ", " $ map headerStr hs
77+ headerStr header = "\"" ++
78+ headerArgName header ++
79+ "\": " ++ show header
80+
81+ fname = req ^. funcName
82+ method = req ^. reqMethod
83+ url = if url' == "'" then "'/'" else url'
84+ url' = "'"
85+ ++ urlArgs
86+ ++ queryArgs
87+
88+ urlArgs = jsSegments
89+ $ req ^.. reqUrl.path.traverse
90+
91+ queryArgs = if null queryparams
92+ then ""
93+ else " + '?" ++ jsParams queryparams
94+
95+printJS :: AjaxReq -> IO ()
96+printJS = putStrLn . generateJS
97+
98+-- | Utility class used by 'jsForAPI' which will
99+-- directly hand you all the Javascript code
100+-- instead of handing you a ':<|>'-separated list
101+-- of 'AjaxReq' like 'jquery' and then having to
102+-- use 'generateJS' on each 'AjaxReq'.
103+class GenerateCode reqs where
104+ jsFor :: reqs -> String
105+
106+instance GenerateCode AjaxReq where
107+ jsFor = generateJS
108+
109+instance GenerateCode rest => GenerateCode (AjaxReq :<|> rest) where
110+ jsFor (req :<|> rest) = jsFor req ++ jsFor rest
111+
112+-- | Directly generate all the javascript functions for your API
113+-- from a 'Proxy' for your API type. You can then write it to
114+-- a file or integrate it in a page, for example.
115+jsForAPI :: (HasJQ (Canonicalize api), GenerateCode (JQ api))
116+ => Proxy api -> String
117+jsForAPI p = jsFor (jquery p)
servant-jquery/src/Servant/JQuery/Internal.hsnew file+339
@@ -0,0 +1,339 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE TypeFamilies #-}
3+{-# LANGUAGE TypeOperators #-}
4+{-# LANGUAGE ConstraintKinds #-}
5+{-# LANGUAGE TemplateHaskell #-}
6+{-# LANGUAGE FlexibleContexts #-}
7+{-# LANGUAGE FlexibleInstances #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE UndecidableInstances #-}
10+module Servant.JQuery.Internal where
11+
12+import Control.Applicative
13+import Control.Lens
14+import Data.Char (toLower)
15+import qualified Data.CharSet as Set
16+import qualified Data.CharSet.Unicode.Category as Set
17+import Data.List
18+import Data.Monoid
19+import Data.Proxy
20+import qualified Data.Text as T
21+import GHC.Exts (Constraint)
22+import GHC.TypeLits
23+import Servant.API
24+
25+type Arg = String
26+
27+data Segment = Segment { _segment :: SegmentType, _matrix :: [MatrixArg] }
28+ deriving (Eq, Show)
29+
30+data SegmentType = Static String -- ^ a static path segment. like "/foo"
31+ | Cap Arg -- ^ a capture. like "/:userid"
32+ deriving (Eq, Show)
33+
34+type Path = [Segment]
35+
36+data ArgType =
37+ Normal
38+ | Flag
39+ | List
40+ deriving (Eq, Show)
41+
42+data QueryArg = QueryArg
43+ { _argName :: Arg
44+ , _argType :: ArgType
45+ } deriving (Eq, Show)
46+
47+data HeaderArg = HeaderArg
48+ { headerArgName :: String
49+ }
50+ | ReplaceHeaderArg
51+ { headerArgName :: String
52+ , headerPattern :: String
53+ } deriving (Eq)
54+
55+instance Show HeaderArg where
56+ show (HeaderArg n) = toValidFunctionName ("header" <> n)
57+ show (ReplaceHeaderArg n p)
58+ | pn `isPrefixOf` p = pv <> " + \"" <> rp <> "\""
59+ | pn `isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
60+ | pn `isInfixOf` p = "\"" <> (replace pn ("\" + " <> pv <> " + \"") p)
61+ <> "\""
62+ | otherwise = p
63+ where
64+ pv = toValidFunctionName ("header" <> n)
65+ pn = "{" <> n <> "}"
66+ rp = replace pn "" p
67+ -- Use replace method from Data.Text
68+ replace old new = T.unpack .
69+ T.replace (T.pack old) (T.pack new) .
70+ T.pack
71+
72+-- | Attempts to reduce the function name provided to that allowed by JS.
73+-- https://mathiasbynens.be/notes/javascript-identifiers
74+-- Couldn't work out how to handle zero-width characters.
75+-- @TODO: specify better default function name, or throw error?
76+toValidFunctionName :: String -> String
77+toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
78+ where
79+ setFirstChar c = if firstChar c
80+ then c
81+ else '_'
82+ firstChar c = (prefixOK c) || (or . map (Set.member c) $ firstLetterOK)
83+ remainder c = (prefixOK c) || (or . map (Set.member c) $ remainderOK)
84+ -- Valid prefixes
85+ prefixOK c = c `elem` ['$','_']
86+ -- Unicode character sets
87+ firstLetterOK = [ Set.lowercaseLetter
88+ , Set.uppercaseLetter
89+ , Set.titlecaseLetter
90+ , Set.modifierLetter
91+ , Set.otherLetter
92+ , Set.letterNumber ]
93+ remainderOK = firstLetterOK <> [ Set.nonSpacingMark
94+ , Set.spacingCombiningMark
95+ , Set.decimalNumber
96+ , Set.connectorPunctuation ]
97+toValidFunctionName [] = "_"
98+
99+type MatrixArg = QueryArg
100+
101+data Url = Url
102+ { _path :: Path
103+ , _queryStr :: [QueryArg]
104+ } deriving (Eq, Show)
105+
106+defUrl :: Url
107+defUrl = Url [] []
108+
109+type FunctionName = String
110+type Method = String
111+
112+data AjaxReq = AjaxReq
113+ { _reqUrl :: Url
114+ , _reqMethod :: Method
115+ , _reqHeaders :: [HeaderArg]
116+ , _reqBody :: Bool
117+ , _funcName :: FunctionName
118+ } deriving (Eq, Show)
119+
120+makeLenses ''QueryArg
121+makeLenses ''Segment
122+makeLenses ''Url
123+makeLenses ''AjaxReq
124+
125+isCapture :: Segment -> Bool
126+isCapture (Segment (Cap _) _) = True
127+isCapture _ = False
128+
129+hasMatrixArgs :: Segment -> Bool
130+hasMatrixArgs (Segment _ (_:_)) = True
131+hasMatrixArgs _ = False
132+
133+hasArgs :: Segment -> Bool
134+hasArgs s = isCapture s || hasMatrixArgs s
135+
136+matrixArgs :: Segment -> [MatrixArg]
137+matrixArgs (Segment _ ms) = ms
138+
139+captureArg :: Segment -> Arg
140+captureArg (Segment (Cap s) _) = s
141+captureArg _ = error "captureArg called on non capture"
142+
143+jsSegments :: [Segment] -> String
144+jsSegments [] = ""
145+jsSegments [x] = "/" ++ segmentToStr x False
146+jsSegments (x:xs) = "/" ++ segmentToStr x True ++ jsSegments xs
147+
148+segmentToStr :: Segment -> Bool -> String
149+segmentToStr (Segment st ms) notTheEnd =
150+ segmentTypeToStr st ++ jsMParams ms ++ if notTheEnd then "" else "'"
151+
152+segmentTypeToStr :: SegmentType -> String
153+segmentTypeToStr (Static s) = s
154+segmentTypeToStr (Cap s) = "' + encodeURIComponent(" ++ s ++ ") + '"
155+
156+jsGParams :: String -> [QueryArg] -> String
157+jsGParams _ [] = ""
158+jsGParams _ [x] = paramToStr x False
159+jsGParams s (x:xs) = paramToStr x True ++ s ++ jsGParams s xs
160+
161+jsParams :: [QueryArg] -> String
162+jsParams = jsGParams "&"
163+
164+jsMParams :: [MatrixArg] -> String
165+jsMParams [] = ""
166+jsMParams xs = ";" ++ jsGParams ";" xs
167+
168+paramToStr :: QueryArg -> Bool -> String
169+paramToStr qarg notTheEnd =
170+ case qarg ^. argType of
171+ Normal -> name
172+ ++ "=' + encodeURIComponent("
173+ ++ name
174+ ++ if notTheEnd then ") + '" else ")"
175+
176+ Flag -> name ++ "="
177+
178+ List -> name
179+ ++ "[]=' + encodeURIComponent("
180+ ++ name
181+ ++ if notTheEnd then ") + '" else ")"
182+
183+ where name = qarg ^. argName
184+
185+defReq :: AjaxReq
186+defReq = AjaxReq defUrl "GET" [] False ""
187+
188+type family Elem (a :: *) (ls::[*]) :: Constraint where
189+ Elem a '[] = 'False ~ 'True
190+ Elem a (a ': list) = ()
191+ Elem a (b ': list) = Elem a list
192+
193+class HasJQ (layout :: *) where
194+ type JQ' layout :: *
195+ jqueryFor :: Proxy layout -> AjaxReq -> JQ' layout
196+
197+type JQ layout = JQ' (Canonicalize layout)
198+
199+instance (HasJQ a, HasJQ b)
200+ => HasJQ (a :<|> b) where
201+ type JQ' (a :<|> b) = JQ' a :<|> JQ' b
202+
203+ jqueryFor Proxy req =
204+ jqueryFor (Proxy :: Proxy a) req
205+ :<|> jqueryFor (Proxy :: Proxy b) req
206+
207+instance (KnownSymbol sym, HasJQ sublayout)
208+ => HasJQ (Capture sym a :> sublayout) where
209+ type JQ' (Capture sym a :> sublayout) = JQ' sublayout
210+
211+ jqueryFor Proxy req =
212+ jqueryFor (Proxy :: Proxy sublayout) $
213+ req & reqUrl.path <>~ [Segment (Cap str) []]
214+
215+ where str = symbolVal (Proxy :: Proxy sym)
216+
217+instance HasJQ Delete where
218+ type JQ' Delete = AjaxReq
219+
220+ jqueryFor Proxy req =
221+ req & funcName %~ ("delete" <>)
222+ & reqMethod .~ "DELETE"
223+
224+instance Elem JSON list => HasJQ (Get list a) where
225+ type JQ' (Get list a) = AjaxReq
226+
227+ jqueryFor Proxy req =
228+ req & funcName %~ ("get" <>)
229+ & reqMethod .~ "GET"
230+
231+instance (KnownSymbol sym, HasJQ sublayout)
232+ => HasJQ (Header sym a :> sublayout) where
233+ type JQ' (Header sym a :> sublayout) = JQ' sublayout
234+
235+ jqueryFor Proxy req =
236+ jqueryFor subP (req & reqHeaders <>~ [HeaderArg hname])
237+
238+ where hname = symbolVal (Proxy :: Proxy sym)
239+ subP = Proxy :: Proxy sublayout
240+
241+instance Elem JSON list => HasJQ (Post list a) where
242+ type JQ' (Post list a) = AjaxReq
243+
244+ jqueryFor Proxy req =
245+ req & funcName %~ ("post" <>)
246+ & reqMethod .~ "POST"
247+
248+instance Elem JSON list => HasJQ (Put list a) where
249+ type JQ' (Put list a) = AjaxReq
250+
251+ jqueryFor Proxy req =
252+ req & funcName %~ ("put" <>)
253+ & reqMethod .~ "PUT"
254+
255+instance (KnownSymbol sym, HasJQ sublayout)
256+ => HasJQ (QueryParam sym a :> sublayout) where
257+ type JQ' (QueryParam sym a :> sublayout) = JQ' sublayout
258+
259+ jqueryFor Proxy req =
260+ jqueryFor (Proxy :: Proxy sublayout) $
261+ req & reqUrl.queryStr <>~ [QueryArg str Normal]
262+
263+ where str = symbolVal (Proxy :: Proxy sym)
264+
265+instance (KnownSymbol sym, HasJQ sublayout)
266+ => HasJQ (QueryParams sym a :> sublayout) where
267+ type JQ' (QueryParams sym a :> sublayout) = JQ' sublayout
268+
269+ jqueryFor Proxy req =
270+ jqueryFor (Proxy :: Proxy sublayout) $
271+ req & reqUrl.queryStr <>~ [QueryArg str List]
272+
273+ where str = symbolVal (Proxy :: Proxy sym)
274+
275+instance (KnownSymbol sym, HasJQ sublayout)
276+ => HasJQ (QueryFlag sym :> sublayout) where
277+ type JQ' (QueryFlag sym :> sublayout) = JQ' sublayout
278+
279+ jqueryFor Proxy req =
280+ jqueryFor (Proxy :: Proxy sublayout) $
281+ req & reqUrl.queryStr <>~ [QueryArg str Flag]
282+
283+ where str = symbolVal (Proxy :: Proxy sym)
284+
285+instance (KnownSymbol sym, HasJQ sublayout)
286+ => HasJQ (MatrixParam sym a :> sublayout) where
287+ type JQ' (MatrixParam sym a :> sublayout) = JQ' sublayout
288+
289+ jqueryFor Proxy req =
290+ jqueryFor (Proxy :: Proxy sublayout) $
291+ req & reqUrl.path._last.matrix <>~ [QueryArg strArg Normal]
292+
293+ where str = symbolVal (Proxy :: Proxy sym)
294+ strArg = str ++ "Value"
295+
296+instance (KnownSymbol sym, HasJQ sublayout)
297+ => HasJQ (MatrixParams sym a :> sublayout) where
298+ type JQ' (MatrixParams sym a :> sublayout) = JQ' sublayout
299+
300+ jqueryFor Proxy req =
301+ jqueryFor (Proxy :: Proxy sublayout) $
302+ req & reqUrl.path._last.matrix <>~ [QueryArg str List]
303+
304+ where str = symbolVal (Proxy :: Proxy sym)
305+
306+instance (KnownSymbol sym, HasJQ sublayout)
307+ => HasJQ (MatrixFlag sym :> sublayout) where
308+ type JQ' (MatrixFlag sym :> sublayout) = JQ' sublayout
309+
310+ jqueryFor Proxy req =
311+ jqueryFor (Proxy :: Proxy sublayout) $
312+ req & reqUrl.path._last.matrix <>~ [QueryArg str Flag]
313+
314+ where str = symbolVal (Proxy :: Proxy sym)
315+
316+instance HasJQ Raw where
317+ type JQ' Raw = Method -> AjaxReq
318+
319+ jqueryFor Proxy req method =
320+ req & funcName %~ ((toLower <$> method) <>)
321+ & reqMethod .~ method
322+
323+instance (Elem JSON list, HasJQ sublayout) => HasJQ (ReqBody list a :> sublayout) where
324+ type JQ' (ReqBody list a :> sublayout) = JQ' sublayout
325+
326+ jqueryFor Proxy req =
327+ jqueryFor (Proxy :: Proxy sublayout) $
328+ req & reqBody .~ True
329+
330+instance (KnownSymbol path, HasJQ sublayout)
331+ => HasJQ (path :> sublayout) where
332+ type JQ' (path :> sublayout) = JQ' sublayout
333+
334+ jqueryFor Proxy req =
335+ jqueryFor (Proxy :: Proxy sublayout) $
336+ req & reqUrl.path <>~ [Segment (Static str) []]
337+ & funcName %~ (str <>)
338+
339+ where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path)
servant-jquery/test/Servant/JQuerySpec.hsnew file+96
@@ -0,0 +1,96 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE QuasiQuotes #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE TypeFamilies #-}
6+{-# LANGUAGE TypeOperators #-}
7+{-# OPTIONS_GHC -fno-warn-orphans #-}
8+module Servant.JQuerySpec where
9+
10+import Data.Either (isRight)
11+import Data.Proxy
12+import Language.ECMAScript3.Parser (parseFromString)
13+import Test.Hspec
14+
15+import Servant.API
16+import Servant.JQuery
17+import Servant.JQuerySpec.CustomHeaders
18+
19+type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool
20+ :<|> "has.extension" :> Get '[FormUrlEncoded,JSON] Bool
21+
22+type TopLevelRawAPI = "something" :> Get '[JSON] Int
23+ :<|> Raw
24+
25+type HeaderHandlingAPI = "something" :> Header "Foo" String
26+ :> Get '[JSON] Int
27+
28+type CustomAuthAPI = "something" :> Authorization "Basic" String
29+ :> Get '[JSON] Int
30+
31+type CustomHeaderAPI = "something" :> MyLovelyHorse String
32+ :> Get '[JSON] Int
33+
34+type CustomHeaderAPI2 = "something" :> WhatsForDinner String
35+ :> Get '[JSON] Int
36+
37+headerHandlingProxy :: Proxy HeaderHandlingAPI
38+headerHandlingProxy = Proxy
39+
40+customAuthProxy :: Proxy CustomAuthAPI
41+customAuthProxy = Proxy
42+
43+customHeaderProxy :: Proxy CustomHeaderAPI
44+customHeaderProxy = Proxy
45+
46+customHeaderProxy2 :: Proxy CustomHeaderAPI2
47+customHeaderProxy2 = Proxy
48+
49+spec :: Spec
50+spec = describe "Servant.JQuery"
51+ generateJSSpec
52+
53+generateJSSpec :: Spec
54+generateJSSpec = describe "generateJS" $ do
55+ it "should generate valid javascript" $ do
56+ let (postSimple :<|> getHasExtension ) = jquery (Proxy :: Proxy TestAPI)
57+ parseFromString (generateJS postSimple) `shouldSatisfy` isRight
58+ parseFromString (generateJS getHasExtension) `shouldSatisfy` isRight
59+ print $ generateJS getHasExtension
60+
61+ it "should use non-empty function names" $ do
62+ let (_ :<|> topLevel) = jquery (Proxy :: Proxy TopLevelRawAPI)
63+ print $ generateJS $ topLevel "GET"
64+ parseFromString (generateJS $ topLevel "GET") `shouldSatisfy` isRight
65+
66+ it "should handle simple HTTP headers" $ do
67+ let jsText = generateJS $ jquery headerHandlingProxy
68+ print jsText
69+ parseFromString jsText `shouldSatisfy` isRight
70+ jsText `shouldContain` "headerFoo"
71+ jsText `shouldContain` "headers: { \"Foo\": headerFoo }\n"
72+
73+ it "should handle complex HTTP headers" $ do
74+ let jsText = generateJS $ jquery customAuthProxy
75+ print jsText
76+ parseFromString jsText `shouldSatisfy` isRight
77+ jsText `shouldContain` "headerAuthorization"
78+ jsText `shouldContain` "headers: { \"Authorization\": \"Basic \" + headerAuthorization }\n"
79+
80+ it "should handle complex, custom HTTP headers" $ do
81+ let jsText = generateJS $ jquery customHeaderProxy
82+ print jsText
83+ parseFromString jsText `shouldSatisfy` isRight
84+ jsText `shouldContain` "headerXMyLovelyHorse"
85+ jsText `shouldContain` "headers: { \"X-MyLovelyHorse\": \"I am good friends with \" + headerXMyLovelyHorse }\n"
86+
87+ it "should handle complex, custom HTTP headers (template replacement)" $ do
88+ let jsText = generateJS $ jquery customHeaderProxy2
89+ print jsText
90+ parseFromString jsText `shouldSatisfy` isRight
91+ jsText `shouldContain` "headerXWhatsForDinner"
92+ jsText `shouldContain` "headers: { \"X-WhatsForDinner\": \"I would like \" + headerXWhatsForDinner + \" with a cherry on top.\" }\n"
93+
94+ it "can generate the whole javascript code string at once with jsForAPI" $ do
95+ let jsStr = jsForAPI (Proxy :: Proxy TestAPI)
96+ parseFromString jsStr `shouldSatisfy` isRight
servant-jquery/test/Servant/JQuerySpec/CustomHeaders.hsnew file+55
@@ -0,0 +1,55 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE KindSignatures #-}
4+{-# LANGUAGE PolyKinds #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeOperators #-}
8+
9+module Servant.JQuerySpec.CustomHeaders where
10+
11+import Control.Lens
12+import Data.Monoid
13+import Data.Proxy
14+import GHC.TypeLits
15+import Servant.API
16+import Servant.JQuery
17+
18+-- | This is a hypothetical combinator that fetches an Authorization header.
19+-- The symbol in the header denotes what kind of authentication we are
20+-- using -- Basic, Digest, whatever.
21+data Authorization (sym :: Symbol) a
22+
23+instance (KnownSymbol sym, HasJQ sublayout)
24+ => HasJQ (Authorization sym a :> sublayout) where
25+ type JQ' (Authorization sym a :> sublayout) = JQ' sublayout
26+
27+ jqueryFor Proxy req = jqueryFor (Proxy :: Proxy sublayout) $
28+ req & reqHeaders <>~ [ ReplaceHeaderArg "Authorization" $
29+ tokenType (symbolVal (Proxy :: Proxy sym)) ]
30+ where
31+ tokenType t = t <> " {Authorization}"
32+
33+-- | This is a combinator that fetches an X-MyLovelyHorse header.
34+data MyLovelyHorse a
35+
36+instance (HasJQ sublayout)
37+ => HasJQ (MyLovelyHorse a :> sublayout) where
38+ type JQ' (MyLovelyHorse a :> sublayout) = JQ' sublayout
39+
40+ jqueryFor Proxy req = jqueryFor (Proxy :: Proxy sublayout) $
41+ req & reqHeaders <>~ [ ReplaceHeaderArg "X-MyLovelyHorse" tpl ]
42+ where
43+ tpl = "I am good friends with {X-MyLovelyHorse}"
44+
45+-- | This is a combinator that fetches an X-WhatsForDinner header.
46+data WhatsForDinner a
47+
48+instance (HasJQ sublayout)
49+ => HasJQ (WhatsForDinner a :> sublayout) where
50+ type JQ' (WhatsForDinner a :> sublayout) = JQ' sublayout
51+
52+ jqueryFor Proxy req = jqueryFor (Proxy :: Proxy sublayout) $
53+ req & reqHeaders <>~ [ ReplaceHeaderArg "X-WhatsForDinner" tpl ]
54+ where
55+ tpl = "I would like {X-WhatsForDinner} with a cherry on top."
servant-jquery/test/Spec.hsnew file+2
@@ -0,0 +1,2 @@
1+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
2+
servant-server/CHANGELOG.mdnew file+26
@@ -0,0 +1,26 @@
1+0.3
2+---
3+* Add a `RouteMismatch` constructor for arbitrary HTTP response codes (https://github.com/haskell-servant/servant-server/pull/22)
4+* Add support for the `Patch` combinator
5+* Support for `Accept`/`Content-type` headers and for the content-type aware combinators in *servant-0.3*
6+* Export `toApplication` from `Servant.Server` (https://github.com/haskell-servant/servant-server/pull/29)
7+* Support other Monads than just `EitherT (Int, String) IO` (https://github.com/haskell-servant/servant-server/pull/21)
8+* Canonicalize API types before generating the handler types with `Server`
9+* Make methods return status code 204 if they return () (https://github.com/haskell-servant/servant-server/issues/28)
10+* Add server support for response headers
11+
12+0.2.4
13+-----
14+* Added support for matrix parameters, see e.g. http://www.w3.org/DesignIssues/MatrixURIs.html
15+* Add support for serializing based on Accept header
16+ (https://github.com/haskell-servant/servant-server/issues/9)
17+* Ignore trailing slashes
18+ (https://github.com/haskell-servant/servant-server/issues/5)
19+
20+
21+0.2.3
22+-----
23+
24+* Fix consuming request body issue
25+ (https://github.com/haskell-servant/servant/issues/3)
26+* Make code sample in Servant.Server complete
servant-server/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2014, Zalora South East Asia Pte Ltd
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Zalora South East Asia Pte Ltd nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-server/README.mdnew file+20
@@ -0,0 +1,20 @@
1+# servant-server
2+
3+[![Build Status](https://secure.travis-ci.org/haskell-servant/servant-server.svg)](http://travis-ci.org/haskell-servant/servant-server)
4+[![Coverage Status](https://coveralls.io/repos/haskell-servant/servant-server/badge.svg)](https://coveralls.io/r/haskell-servant/servant-server)
5+
6+![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
7+
8+This library lets you *implement* an HTTP server with handlers for each endpoint of a servant API, handling most of the boilerplate for you.
9+
10+## Getting started
11+
12+We've written a [Getting Started](http://haskell-servant.github.io/getting-started/) guide that introduces the core types and features of servant. After this article, you should be able to write your first servant webservices, learning the rest from the haddocks' examples.
13+
14+## Repositories and Haddocks
15+
16+- The core [servant](http://github.com/haskell-servant) package - [docs](http://hackage.haskell.org/package/servant)
17+- Implementing an HTTP server for a webservice API with [servant-server](http://github.com/haskell-servant/servant-server) - [docs](http://hackage.haskell.org/package/servant-server)
18+- (Haskell) client-side function generation with [servant-client](http://github.com/haskell-servant/servant-client) - [docs](http://hackage.haskell.org/package/servant-client)
19+- (Javascript) client-side function generation with [servant-jquery](http://github.com/haskell-servant/servant-jquery) - [docs](http://hackage.haskell.org/package/servant-jquery)
20+- API docs generation with [servant-docs](http://github.com/haskell-servant/servant-docs) - [docs](http://hackage.haskell.org/package/servant-docs)
servant-server/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
servant-server/default.nixnew file+15
@@ -0,0 +1,15 @@
1+{ pkgs ? import <nixpkgs> { config.allowUnfree = true; }
2+, src ? builtins.filterSource (path: type:
3+ type != "unknown" &&
4+ baseNameOf path != ".git" &&
5+ baseNameOf path != "result" &&
6+ baseNameOf path != "dist") ./.
7+, servant ? import ../servant {}
8+}:
9+pkgs.haskellPackages.buildLocalCabalWithArgs {
10+ name = "servant-server";
11+ inherit src;
12+ args = {
13+ inherit servant;
14+ };
15+}
servant-server/example/README.mdnew file+2
@@ -0,0 +1,2 @@
1+- `greet.hs` shows how to write a simple webservice, run it, query it with automatically-derived haskell functions and print the (generated) markdown documentation for the API.
2+- `greet.md` contains the aforementionned generated documentation.
servant-server/example/greet.hsnew file+72
@@ -0,0 +1,72 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE PolyKinds #-}
3+{-# LANGUAGE TypeFamilies #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE TypeOperators #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+
8+import Data.Aeson
9+import Data.Monoid
10+import Data.Proxy
11+import Data.Text
12+import GHC.Generics
13+import Network.Wai
14+import Network.Wai.Handler.Warp
15+
16+import Servant
17+
18+-- * Example
19+
20+-- | A greet message data type
21+newtype Greet = Greet { _msg :: Text }
22+ deriving (Generic, Show)
23+
24+instance FromJSON Greet
25+instance ToJSON Greet
26+
27+-- API specification
28+type TestApi =
29+ -- GET /hello/:name?capital={true, false} returns a Greet as JSON
30+ "hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON] Greet
31+
32+ -- POST /greet with a Greet as JSON in the request body,
33+ -- returns a Greet as JSON
34+ :<|> "greet" :> ReqBody '[JSON] Greet :> Post '[JSON] Greet
35+
36+ -- DELETE /greet/:greetid
37+ :<|> "greet" :> Capture "greetid" Text :> Delete
38+
39+testApi :: Proxy TestApi
40+testApi = Proxy
41+
42+-- Server-side handlers.
43+--
44+-- There's one handler per endpoint, which, just like in the type
45+-- that represents the API, are glued together using :<|>.
46+--
47+-- Each handler runs in the 'EitherT (Int, String) IO' monad.
48+server :: Server TestApi
49+server = helloH :<|> postGreetH :<|> deleteGreetH
50+
51+ where helloH name Nothing = helloH name (Just False)
52+ helloH name (Just False) = return . Greet $ "Hello, " <> name
53+ helloH name (Just True) = return . Greet . toUpper $ "Hello, " <> name
54+
55+ postGreetH greet = return greet
56+
57+ deleteGreetH _ = return ()
58+
59+-- Turn the server into a WAI app. 'serve' is provided by servant,
60+-- more precisely by the Servant.Server module.
61+test :: Application
62+test = serve testApi server
63+
64+-- Run the server.
65+--
66+-- 'run' comes from Network.Wai.Handler.Warp
67+runTestServer :: Port -> IO ()
68+runTestServer port = run port test
69+
70+-- Put this all to work!
71+main :: IO ()
72+main = runTestServer 8001
servant-server/example/greet.mdnew file+52
@@ -0,0 +1,52 @@
1+POST /greet
2+-----------
3+
4+**Request Body**:
5+
6+``` javascript
7+{"msg":"Hello, haskeller!"}
8+```
9+
10+**Response**:
11+
12+ - Status code 201
13+ - Response body as below.
14+
15+``` javascript
16+{"msg":"Hello, haskeller!"}
17+```
18+
19+GET /hello/:name
20+----------------
21+
22+**Captures**:
23+
24+- *name*: name of the person to greet
25+
26+**GET Parameters**:
27+
28+ - capital
29+ - **Values**: *true, false*
30+ - **Description**: Get the greeting message in uppercase (true) or not (false). Default is false.
31+
32+
33+**Response**:
34+
35+ - Status code 200
36+ - Response body as below.
37+
38+``` javascript
39+{"msg":"Hello, haskeller!"}
40+```
41+
42+DELETE /greet/:greetid
43+----------------------
44+
45+**Captures**:
46+
47+- *greetid*: identifier of the greet msg to remove
48+
49+**Response**:
50+
51+ - Status code 204
52+ - No response body
servant-server/servant-server.cabalnew file+101
@@ -0,0 +1,101 @@
1+name: servant-server
2+version: 0.2.4
3+synopsis: A family of combinators for defining webservices APIs and serving them
4+description:
5+ A family of combinators for defining webservices APIs and serving them
6+ .
7+ You can learn about the basics in <http://haskell-servant.github.io/getting-started/ the getting started> guide.
8+ .
9+ <https://github.com/haskell-servant/servant-server/blob/master/example/greet.hs Here>'s a runnable example, with comments, that defines a dummy API and
10+ implements a webserver that serves this API, using this package.
11+homepage: http://haskell-servant.github.io/
12+Bug-reports: http://github.com/haskell-servant/servant-server/issues
13+license: BSD3
14+license-file: LICENSE
15+author: Alp Mestanogullari, Sönke Hahn, Julian K. Arni
16+maintainer: alpmestan@gmail.com
17+copyright: 2014 Zalora South East Asia Pte Ltd
18+category: Web
19+build-type: Simple
20+cabal-version: >=1.10
21+tested-with: GHC >= 7.8
22+extra-source-files:
23+ CHANGELOG.md
24+ README.md
25+source-repository head
26+ type: git
27+ location: http://github.com/haskell-servant/servant-server.git
28+
29+
30+library
31+ exposed-modules:
32+ Servant
33+ Servant.Server
34+ Servant.Server.Internal
35+ Servant.Utils.StaticFiles
36+ build-depends:
37+ base >= 4.7 && < 5
38+ , aeson >= 0.7 && < 0.9
39+ , attoparsec >= 0.12 && < 0.13
40+ , bytestring >= 0.10 && < 0.11
41+ , either >= 4.3 && < 4.4
42+ , http-types >= 0.8 && < 0.9
43+ , network-uri >= 2.6 && < 2.7
44+ , safe >= 0.3 && < 0.4
45+ , servant >= 0.2 && < 0.4
46+ , split >= 0.2 && < 0.3
47+ , string-conversions >= 0.3 && < 0.4
48+ , system-filepath >= 0.4 && < 0.5
49+ , text >= 1.2 && < 1.3
50+ , transformers >= 0.3 && < 0.5
51+ , wai >= 3.0 && < 3.1
52+ , wai-app-static >= 3.0 && < 3.1
53+ , warp >= 3.0 && < 3.1
54+ hs-source-dirs: src
55+ default-language: Haskell2010
56+ ghc-options: -Wall
57+
58+executable greet
59+ main-is: greet.hs
60+ hs-source-dirs: example
61+ ghc-options: -Wall
62+ default-language: Haskell2010
63+ build-depends:
64+ base
65+ , servant
66+ , servant-server
67+ , aeson
68+ , warp
69+ , wai
70+ , text
71+
72+test-suite spec
73+ type: exitcode-stdio-1.0
74+ ghc-options:
75+ -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures
76+ default-language: Haskell2010
77+ hs-source-dirs: test
78+ main-is: Spec.hs
79+ build-depends:
80+ base == 4.*
81+ , aeson
82+ , bytestring
83+ , bytestring-conversion
84+ , directory
85+ , either
86+ , exceptions
87+ , hspec == 2.*
88+ , hspec-wai
89+ , http-types
90+ , network >= 2.6
91+ , QuickCheck
92+ , parsec
93+ , servant
94+ , servant-server
95+ , string-conversions
96+ , temporary
97+ , text
98+ , transformers
99+ , wai
100+ , wai-extra
101+ , warp
servant-server/src/Servant.hsnew file+22
@@ -0,0 +1,22 @@
1+module Servant (
2+ -- | This module and its submodules can be used to define servant APIs. Note
3+ -- that these API definitions don't directly implement a server (or anything
4+ -- else).
5+ module Servant.API,
6+ -- | For implementing servers for servant APIs.
7+ module Servant.Server,
8+ -- | Using your types in request paths and query string parameters
9+ module Servant.Common.Text,
10+ -- | Utilities on top of the servant core
11+ module Servant.Utils.Links,
12+ module Servant.Utils.StaticFiles,
13+ -- | Useful re-exports
14+ Proxy(..),
15+ ) where
16+
17+import Data.Proxy
18+import Servant.API
19+import Servant.Common.Text
20+import Servant.Server
21+import Servant.Utils.Links
22+import Servant.Utils.StaticFiles
servant-server/src/Servant/Server.hsnew file+49
@@ -0,0 +1,49 @@
1+{-# LANGUAGE TypeFamilies #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+
5+-- | This module lets you implement 'Server's for defined APIs. You'll
6+-- most likely just need 'serve'.
7+module Servant.Server
8+ ( -- * Run a wai application from an API
9+ serve
10+
11+ , -- * Construct a wai Application from an API
12+ toApplication
13+
14+ , -- * Handlers for all standard combinators
15+ HasServer(..)
16+ , Server
17+ , ServerT
18+ ) where
19+
20+import Data.Proxy (Proxy)
21+import Network.Wai (Application)
22+import Servant.API (Canonicalize, canonicalize)
23+import Servant.Server.Internal
24+
25+
26+-- * Implementing Servers
27+
28+-- | 'serve' allows you to implement an API and produce a wai 'Application'.
29+--
30+-- Example:
31+--
32+-- > type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
33+-- > :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books
34+-- >
35+-- > server :: Server MyApi
36+-- > server = listAllBooks :<|> postBook
37+-- > where listAllBooks = ...
38+-- > postBook book = ...
39+-- >
40+-- > myApi :: Proxy MyApi
41+-- > myApi = Proxy
42+-- >
43+-- > app :: Application
44+-- > app = serve myApi server
45+-- >
46+-- > main :: IO ()
47+-- > main = Network.Wai.Handler.Warp.run 8080 app
48+serve :: HasServer (Canonicalize layout) => Proxy layout -> Server layout -> Application
49+serve p server = toApplication (route (canonicalize p) server)
servant-server/src/Servant/Server/Internal.hsnew file+866
@@ -0,0 +1,866 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE OverlappingInstances #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE PolyKinds #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE TypeOperators #-}
10+module Servant.Server.Internal where
11+
12+import Control.Applicative ((<$>))
13+import Control.Monad.Trans.Either (EitherT, runEitherT)
14+import qualified Data.ByteString as B
15+import qualified Data.ByteString.Lazy as BL
16+import Data.IORef (newIORef, readIORef, writeIORef)
17+import Data.List (unfoldr)
18+import Data.Maybe (catMaybes, fromMaybe)
19+import Data.Monoid (Monoid, mappend, mempty)
20+import Data.String (fromString)
21+import Data.String.Conversions (cs, (<>))
22+import Data.Text (Text)
23+import qualified Data.Text as T
24+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
25+import Data.Typeable
26+import GHC.TypeLits (KnownSymbol, symbolVal)
27+import Network.HTTP.Types hiding (Header, ResponseHeaders)
28+import Network.Wai (Application, Request, Response,
29+ ResponseReceived, lazyRequestBody,
30+ pathInfo, rawQueryString,
31+ requestBody, requestHeaders,
32+ requestMethod, responseLBS,
33+ strictRequestBody)
34+import Servant.API ((:<|>) (..), (:>), Capture,
35+ Canonicalize, Delete, Get, Header,
36+ MatrixFlag, MatrixParam, MatrixParams,
37+ Patch, Post, Put, QueryFlag,
38+ QueryParam, QueryParams, Raw,
39+ ReqBody)
40+import Servant.API.ContentTypes (AcceptHeader (..),
41+ AllCTRender (..),
42+ AllCTUnrender (..))
43+import Servant.API.ResponseHeaders (Headers, getResponse, getHeaders)
44+import Servant.Common.Text (FromText, fromText)
45+
46+data ReqBodyState = Uncalled
47+ | Called !B.ByteString
48+ | Done !B.ByteString
49+
50+
51+toApplication :: RoutingApplication -> Application
52+toApplication ra request respond = do
53+ reqBodyRef <- newIORef Uncalled
54+ -- We may need to consume the requestBody more than once. In order to
55+ -- maintain the illusion that 'requestBody' works as expected,
56+ -- 'ReqBodyState' is introduced, and the complete body is memoized and
57+ -- returned as many times as requested with empty "Done" marker chunks in
58+ -- between.
59+ -- See https://github.com/haskell-servant/servant/issues/3
60+ let memoReqBody = do
61+ ior <- readIORef reqBodyRef
62+ case ior of
63+ Uncalled -> do
64+ r <- BL.toStrict <$> strictRequestBody request
65+ writeIORef reqBodyRef $ Done r
66+ return r
67+ Called bs -> do
68+ writeIORef reqBodyRef $ Done bs
69+ return bs
70+ Done bs -> do
71+ writeIORef reqBodyRef $ Called bs
72+ return B.empty
73+
74+ ra request{ requestBody = memoReqBody } (routingRespond . routeResult)
75+ where
76+ routingRespond :: Either RouteMismatch Response -> IO ResponseReceived
77+ routingRespond (Left NotFound) =
78+ respond $ responseLBS notFound404 [] "not found"
79+ routingRespond (Left WrongMethod) =
80+ respond $ responseLBS methodNotAllowed405 [] "method not allowed"
81+ routingRespond (Left (InvalidBody err)) =
82+ respond $ responseLBS badRequest400 [] $ fromString $ "invalid request body: " ++ err
83+ routingRespond (Left UnsupportedMediaType) =
84+ respond $ responseLBS unsupportedMediaType415 [] "unsupported media type"
85+ routingRespond (Left (HttpError status body)) =
86+ respond $ responseLBS status [] $ fromMaybe (BL.fromStrict $ statusMessage status) body
87+ routingRespond (Right response) =
88+ respond response
89+
90+-- Note that the ordering of the constructors has great significance! It
91+-- determines the Ord instance and, consequently, the monoid instance.
92+-- * Route mismatch
93+data RouteMismatch =
94+ NotFound -- ^ the usual "not found" error
95+ | WrongMethod -- ^ a more informative "you just got the HTTP method wrong" error
96+ | UnsupportedMediaType -- ^ request body has unsupported media type
97+ | InvalidBody String -- ^ an even more informative "your json request body wasn't valid" error
98+ | HttpError Status (Maybe BL.ByteString) -- ^ an even even more informative arbitrary HTTP response code error.
99+ deriving (Eq, Ord, Show)
100+
101+instance Monoid RouteMismatch where
102+ mempty = NotFound
103+ -- The following isn't great, since it picks @InvalidBody@ based on
104+ -- alphabetical ordering, but any choice would be arbitrary.
105+ --
106+ -- "As one judge said to the other, 'Be just and if you can't be just, be
107+ -- arbitrary'" -- William Burroughs
108+ mappend = max
109+
110+
111+-- | A wrapper around @'Either' 'RouteMismatch' a@.
112+newtype RouteResult a =
113+ RR { routeResult :: Either RouteMismatch a }
114+ deriving (Eq, Show)
115+
116+failWith :: RouteMismatch -> RouteResult a
117+failWith = RR . Left
118+
119+succeedWith :: a -> RouteResult a
120+succeedWith = RR . Right
121+
122+isMismatch :: RouteResult a -> Bool
123+isMismatch (RR (Left _)) = True
124+isMismatch _ = False
125+
126+-- | Like `null . pathInfo`, but works with redundant trailing slashes.
127+pathIsEmpty :: Request -> Bool
128+pathIsEmpty = f . processedPathInfo
129+ where
130+ f [] = True
131+ f [""] = True
132+ f _ = False
133+
134+-- | If we get a `Right`, it has precedence over everything else.
135+--
136+-- This in particular means that if we could get several 'Right's,
137+-- only the first we encounter would be taken into account.
138+instance Monoid (RouteResult a) where
139+ mempty = RR $ Left mempty
140+
141+ RR (Left x) `mappend` RR (Left y) = RR $ Left (x <> y)
142+ RR (Left _) `mappend` RR (Right y) = RR $ Right y
143+ r `mappend` _ = r
144+
145+type RoutingApplication =
146+ Request -- ^ the request, the field 'pathInfo' may be modified by url routing
147+ -> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived
148+
149+splitMatrixParameters :: Text -> (Text, Text)
150+splitMatrixParameters = T.break (== ';')
151+
152+parsePathInfo :: Request -> [Text]
153+parsePathInfo = filter (/= "") . mergePairs . map splitMatrixParameters . pathInfo
154+ where mergePairs = concat . unfoldr pairToList
155+ pairToList [] = Nothing
156+ pairToList ((a, b):xs) = Just ([a, b], xs)
157+
158+-- | Returns a processed pathInfo from the request.
159+--
160+-- In order to handle matrix parameters in the request correctly, the raw pathInfo needs to be
161+-- processed, so routing works as intended. Therefor this function should be used to access
162+-- the pathInfo for routing purposes.
163+processedPathInfo :: Request -> [Text]
164+processedPathInfo r =
165+ case pinfo of
166+ (x:xs) | T.head x == ';' -> xs
167+ _ -> pinfo
168+ where pinfo = parsePathInfo r
169+
170+class HasServer layout where
171+ type ServerT' layout (m :: * -> *) :: *
172+
173+ route :: Proxy layout -> Server' layout -> RoutingApplication
174+
175+type Server layout = Server' (Canonicalize layout)
176+type Server' layout = ServerT' layout (EitherT (Int, String) IO)
177+type ServerT layout m = ServerT' (Canonicalize layout) m
178+
179+-- * Instances
180+
181+-- | A server for @a ':<|>' b@ first tries to match the request against the route
182+-- represented by @a@ and if it fails tries @b@. You must provide a request
183+-- handler for each route.
184+--
185+-- > type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
186+-- > :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books
187+-- >
188+-- > server :: Server MyApi
189+-- > server = listAllBooks :<|> postBook
190+-- > where listAllBooks = ...
191+-- > postBook book = ...
192+instance (HasServer a, HasServer b) => HasServer (a :<|> b) where
193+
194+ type ServerT' (a :<|> b) m = ServerT' a m :<|> ServerT' b m
195+
196+ route Proxy (a :<|> b) request respond =
197+ route pa a request $ \mResponse ->
198+ if isMismatch mResponse
199+ then route pb b request $ \mResponse' -> respond (mResponse <> mResponse')
200+ else respond mResponse
201+
202+ where pa = Proxy :: Proxy a
203+ pb = Proxy :: Proxy b
204+
205+captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a
206+captured _ = fromText
207+
208+-- | If you use 'Capture' in one of the endpoints for your API,
209+-- this automatically requires your server-side handler to be a function
210+-- that takes an argument of the type specified by the 'Capture'.
211+-- This lets servant worry about getting it from the URL and turning
212+-- it into a value of the type you specify.
213+--
214+-- You can control how it'll be converted from 'Text' to your type
215+-- by simply providing an instance of 'FromText' for your type.
216+--
217+-- Example:
218+--
219+-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
220+-- >
221+-- > server :: Server MyApi
222+-- > server = getBook
223+-- > where getBook :: Text -> EitherT (Int, String) IO Book
224+-- > getBook isbn = ...
225+instance (KnownSymbol capture, FromText a, HasServer sublayout)
226+ => HasServer (Capture capture a :> sublayout) where
227+
228+ type ServerT' (Capture capture a :> sublayout) m =
229+ a -> ServerT' sublayout m
230+
231+ route Proxy subserver request respond = case processedPathInfo request of
232+ (first : rest)
233+ -> case captured captureProxy first of
234+ Nothing -> respond $ failWith NotFound
235+ Just v -> route (Proxy :: Proxy sublayout) (subserver v) request{
236+ pathInfo = rest
237+ } respond
238+ _ -> respond $ failWith NotFound
239+
240+ where captureProxy = Proxy :: Proxy (Capture capture a)
241+
242+
243+-- | If you have a 'Delete' endpoint in your API,
244+-- the handler for this endpoint is meant to delete
245+-- a resource.
246+--
247+-- The code of the handler will, just like
248+-- for 'Servant.API.Get.Get', 'Servant.API.Post.Post' and
249+-- 'Servant.API.Put.Put', run in @EitherT (Int, String) IO ()@.
250+-- The 'Int' represents the status code and the 'String' a message
251+-- to be returned. You can use 'Control.Monad.Trans.Either.left' to
252+-- painlessly error out if the conditions for a successful deletion
253+-- are not met.
254+instance HasServer Delete where
255+
256+ type ServerT' Delete m = m ()
257+
258+ route Proxy action request respond
259+ | pathIsEmpty request && requestMethod request == methodDelete = do
260+ e <- runEitherT action
261+ respond $ succeedWith $ case e of
262+ Right () ->
263+ responseLBS status204 [] ""
264+ Left (status, message) ->
265+ responseLBS (mkStatus status (cs message)) [] (cs message)
266+ | pathIsEmpty request && requestMethod request /= methodDelete =
267+ respond $ failWith WrongMethod
268+ | otherwise = respond $ failWith NotFound
269+
270+-- | When implementing the handler for a 'Get' endpoint,
271+-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Post.Post'
272+-- and 'Servant.API.Put.Put', the handler code runs in the
273+-- @EitherT (Int, String) IO@ monad, where the 'Int' represents
274+-- the status code and the 'String' a message, returned in case of
275+-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
276+-- to quickly fail if some conditions are not met.
277+--
278+-- If successfully returning a value, we use the type-level list, combined
279+-- with the request's @Accept@ header, to encode the value for you
280+-- (returning a status code of 200). If there was no @Accept@ header or it
281+-- was @*/*@, we return encode using the first @Content-Type@ type on the
282+-- list.
283+instance ( AllCTRender ctypes a
284+ ) => HasServer (Get ctypes a) where
285+
286+ type ServerT' (Get ctypes a) m = m a
287+
288+ route Proxy action request respond
289+ | pathIsEmpty request && requestMethod request == methodGet = do
290+ e <- runEitherT action
291+ respond $ case e of
292+ Right output -> do
293+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
294+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
295+ Nothing -> failWith UnsupportedMediaType
296+ Just (contentT, body) -> succeedWith $
297+ responseLBS ok200 [ ("Content-Type" , cs contentT)] body
298+ Left (status, message) -> succeedWith $
299+ responseLBS (mkStatus status (cs message)) [] (cs message)
300+ | pathIsEmpty request && requestMethod request /= methodGet =
301+ respond $ failWith WrongMethod
302+ | otherwise = respond $ failWith NotFound
303+
304+-- '()' ==> 204 No Content
305+instance HasServer (Get ctypes ()) where
306+ type ServerT' (Get ctypes ()) m = m ()
307+ route Proxy action request respond
308+ | pathIsEmpty request && requestMethod request == methodGet = do
309+ e <- runEitherT action
310+ respond . succeedWith $ case e of
311+ Right () -> responseLBS noContent204 [] ""
312+ Left (status, message) ->
313+ responseLBS (mkStatus status (cs message)) [] (cs message)
314+ | pathIsEmpty request && requestMethod request /= methodGet =
315+ respond $ failWith WrongMethod
316+ | otherwise = respond $ failWith NotFound
317+
318+-- Add response headers
319+instance ( AllCTRender ctypes v ) => HasServer (Get ctypes (Headers h v)) where
320+ type ServerT' (Get ctypes (Headers h v)) m = m (Headers h v)
321+ route Proxy action request respond
322+ | pathIsEmpty request && requestMethod request == methodGet = do
323+ e <- runEitherT action
324+ respond $ case e of
325+ Right output -> do
326+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
327+ headers = getHeaders output
328+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
329+ Nothing -> failWith UnsupportedMediaType
330+ Just (contentT, body) -> succeedWith $
331+ responseLBS ok200 ( ("Content-Type" , cs contentT) : headers) body
332+ Left (status, message) -> succeedWith $
333+ responseLBS (mkStatus status (cs message)) [] (cs message)
334+ | pathIsEmpty request && requestMethod request /= methodGet =
335+ respond $ failWith WrongMethod
336+ | otherwise = respond $ failWith NotFound
337+
338+-- | If you use 'Header' in one of the endpoints for your API,
339+-- this automatically requires your server-side handler to be a function
340+-- that takes an argument of the type specified by 'Header'.
341+-- This lets servant worry about extracting it from the request and turning
342+-- it into a value of the type you specify.
343+--
344+-- All it asks is for a 'FromText' instance.
345+--
346+-- Example:
347+--
348+-- > newtype Referer = Referer Text
349+-- > deriving (Eq, Show, FromText, ToText)
350+-- >
351+-- > -- GET /view-my-referer
352+-- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get '[JSON] Referer
353+-- >
354+-- > server :: Server MyApi
355+-- > server = viewReferer
356+-- > where viewReferer :: Referer -> EitherT (Int, String) IO referer
357+-- > viewReferer referer = return referer
358+instance (KnownSymbol sym, FromText a, HasServer sublayout)
359+ => HasServer (Header sym a :> sublayout) where
360+
361+ type ServerT' (Header sym a :> sublayout) m =
362+ Maybe a -> ServerT' sublayout m
363+
364+ route Proxy subserver request respond = do
365+ let mheader = fromText . decodeUtf8 =<< lookup str (requestHeaders request)
366+ route (Proxy :: Proxy sublayout) (subserver mheader) request respond
367+
368+ where str = fromString $ symbolVal (Proxy :: Proxy sym)
369+
370+-- | When implementing the handler for a 'Post' endpoint,
371+-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
372+-- and 'Servant.API.Put.Put', the handler code runs in the
373+-- @EitherT (Int, String) IO@ monad, where the 'Int' represents
374+-- the status code and the 'String' a message, returned in case of
375+-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
376+-- to quickly fail if some conditions are not met.
377+--
378+-- If successfully returning a value, we use the type-level list, combined
379+-- with the request's @Accept@ header, to encode the value for you
380+-- (returning a status code of 201). If there was no @Accept@ header or it
381+-- was @*/*@, we return encode using the first @Content-Type@ type on the
382+-- list.
383+instance ( AllCTRender ctypes a
384+ ) => HasServer (Post ctypes a) where
385+
386+ type ServerT' (Post ctypes a) m = m a
387+
388+ route Proxy action request respond
389+ | pathIsEmpty request && requestMethod request == methodPost = do
390+ e <- runEitherT action
391+ respond $ case e of
392+ Right output -> do
393+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
394+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
395+ Nothing -> failWith UnsupportedMediaType
396+ Just (contentT, body) -> succeedWith $
397+ responseLBS status201 [ ("Content-Type" , cs contentT)] body
398+ Left (status, message) -> succeedWith $
399+ responseLBS (mkStatus status (cs message)) [] (cs message)
400+ | pathIsEmpty request && requestMethod request /= methodPost =
401+ respond $ failWith WrongMethod
402+ | otherwise = respond $ failWith NotFound
403+
404+instance HasServer (Post ctypes ()) where
405+ type ServerT' (Post ctypes ()) m = m ()
406+ route Proxy action request respond
407+ | pathIsEmpty request && requestMethod request == methodPost = do
408+ e <- runEitherT action
409+ respond . succeedWith $ case e of
410+ Right () -> responseLBS noContent204 [] ""
411+ Left (status, message) ->
412+ responseLBS (mkStatus status (cs message)) [] (cs message)
413+ | pathIsEmpty request && requestMethod request /= methodPost =
414+ respond $ failWith WrongMethod
415+ | otherwise = respond $ failWith NotFound
416+
417+-- Add response headers
418+instance ( AllCTRender ctypes v ) => HasServer (Post ctypes (Headers h v)) where
419+ type ServerT' (Post ctypes (Headers h v)) m = m (Headers h v)
420+ route Proxy action request respond
421+ | pathIsEmpty request && requestMethod request == methodPost = do
422+ e <- runEitherT action
423+ respond $ case e of
424+ Right output -> do
425+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
426+ headers = getHeaders output
427+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
428+ Nothing -> failWith UnsupportedMediaType
429+ Just (contentT, body) -> succeedWith $
430+ responseLBS status201 ( ("Content-Type" , cs contentT) : headers) body
431+ Left (status, message) -> succeedWith $
432+ responseLBS (mkStatus status (cs message)) [] (cs message)
433+ | pathIsEmpty request && requestMethod request /= methodPost =
434+ respond $ failWith WrongMethod
435+ | otherwise = respond $ failWith NotFound
436+
437+-- | When implementing the handler for a 'Put' endpoint,
438+-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
439+-- and 'Servant.API.Post.Post', the handler code runs in the
440+-- @EitherT (Int, String) IO@ monad, where the 'Int' represents
441+-- the status code and the 'String' a message, returned in case of
442+-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
443+-- to quickly fail if some conditions are not met.
444+--
445+-- If successfully returning a value, we use the type-level list, combined
446+-- with the request's @Accept@ header, to encode the value for you
447+-- (returning a status code of 200). If there was no @Accept@ header or it
448+-- was @*/*@, we return encode using the first @Content-Type@ type on the
449+-- list.
450+instance ( AllCTRender ctypes a
451+ ) => HasServer (Put ctypes a) where
452+
453+ type ServerT' (Put ctypes a) m = m a
454+
455+ route Proxy action request respond
456+ | pathIsEmpty request && requestMethod request == methodPut = do
457+ e <- runEitherT action
458+ respond $ case e of
459+ Right output -> do
460+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
461+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
462+ Nothing -> failWith UnsupportedMediaType
463+ Just (contentT, body) -> succeedWith $
464+ responseLBS status200 [ ("Content-Type" , cs contentT)] body
465+ Left (status, message) -> succeedWith $
466+ responseLBS (mkStatus status (cs message)) [] (cs message)
467+ | pathIsEmpty request && requestMethod request /= methodPut =
468+ respond $ failWith WrongMethod
469+ | otherwise = respond $ failWith NotFound
470+
471+instance HasServer (Put ctypes ()) where
472+ type ServerT' (Put ctypes ()) m = m ()
473+ route Proxy action request respond
474+ | pathIsEmpty request && requestMethod request == methodPut = do
475+ e <- runEitherT action
476+ respond . succeedWith $ case e of
477+ Right () -> responseLBS noContent204 [] ""
478+ Left (status, message) ->
479+ responseLBS (mkStatus status (cs message)) [] (cs message)
480+ | pathIsEmpty request && requestMethod request /= methodPut =
481+ respond $ failWith WrongMethod
482+ | otherwise = respond $ failWith NotFound
483+
484+-- Add response headers
485+instance ( AllCTRender ctypes v ) => HasServer (Put ctypes (Headers h v)) where
486+ type ServerT' (Put ctypes (Headers h v)) m = m (Headers h v)
487+ route Proxy action request respond
488+ | pathIsEmpty request && requestMethod request == methodPut = do
489+ e <- runEitherT action
490+ respond $ case e of
491+ Right output -> do
492+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
493+ headers = getHeaders output
494+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
495+ Nothing -> failWith UnsupportedMediaType
496+ Just (contentT, body) -> succeedWith $
497+ responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
498+ Left (status, message) -> succeedWith $
499+ responseLBS (mkStatus status (cs message)) [] (cs message)
500+ | pathIsEmpty request && requestMethod request /= methodPut =
501+ respond $ failWith WrongMethod
502+ | otherwise = respond $ failWith NotFound
503+
504+-- | When implementing the handler for a 'Patch' endpoint,
505+-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
506+-- and 'Servant.API.Put.Put', the handler code runs in the
507+-- @EitherT (Int, String) IO@ monad, where the 'Int' represents
508+-- the status code and the 'String' a message, returned in case of
509+-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
510+-- to quickly fail if some conditions are not met.
511+--
512+-- If successfully returning a value, we just require that its type has
513+-- a 'ToJSON' instance and servant takes care of encoding it for you,
514+-- yielding status code 200 along the way.
515+instance ( AllCTRender ctypes a
516+ ) => HasServer (Patch ctypes a) where
517+ type ServerT' (Patch ctypes a) m = m a
518+
519+ route Proxy action request respond
520+ | pathIsEmpty request && requestMethod request == methodPatch = do
521+ e <- runEitherT action
522+ respond $ case e of
523+ Right output -> do
524+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
525+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
526+ Nothing -> failWith UnsupportedMediaType
527+ Just (contentT, body) -> succeedWith $
528+ responseLBS status200 [ ("Content-Type" , cs contentT)] body
529+ Left (status, message) -> succeedWith $
530+ responseLBS (mkStatus status (cs message)) [] (cs message)
531+ | pathIsEmpty request && requestMethod request /= methodPatch =
532+ respond $ failWith WrongMethod
533+ | otherwise = respond $ failWith NotFound
534+
535+instance HasServer (Patch ctypes ()) where
536+ type ServerT' (Patch ctypes ()) m = m ()
537+ route Proxy action request respond
538+ | pathIsEmpty request && requestMethod request == methodPatch = do
539+ e <- runEitherT action
540+ respond . succeedWith $ case e of
541+ Right () -> responseLBS noContent204 [] ""
542+ Left (status, message) ->
543+ responseLBS (mkStatus status (cs message)) [] (cs message)
544+ | pathIsEmpty request && requestMethod request /= methodPatch =
545+ respond $ failWith WrongMethod
546+ | otherwise = respond $ failWith NotFound
547+
548+-- Add response headers
549+instance ( AllCTRender ctypes v ) => HasServer (Patch ctypes (Headers h v)) where
550+ type ServerT' (Patch ctypes (Headers h v)) m = m (Headers h v)
551+ route Proxy action request respond
552+ | pathIsEmpty request && requestMethod request == methodPatch = do
553+ e <- runEitherT action
554+ respond $ case e of
555+ Right outpatch -> do
556+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
557+ headers = getHeaders outpatch
558+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse outpatch) of
559+ Nothing -> failWith UnsupportedMediaType
560+ Just (contentT, body) -> succeedWith $
561+ responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
562+ Left (status, message) -> succeedWith $
563+ responseLBS (mkStatus status (cs message)) [] (cs message)
564+ | pathIsEmpty request && requestMethod request /= methodPatch =
565+ respond $ failWith WrongMethod
566+ | otherwise = respond $ failWith NotFound
567+
568+-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
569+-- this automatically requires your server-side handler to be a function
570+-- that takes an argument of type @'Maybe' 'Text'@.
571+--
572+-- This lets servant worry about looking it up in the query string
573+-- and turning it into a value of the type you specify, enclosed
574+-- in 'Maybe', because it may not be there and servant would then
575+-- hand you 'Nothing'.
576+--
577+-- You can control how it'll be converted from 'Text' to your type
578+-- by simply providing an instance of 'FromText' for your type.
579+--
580+-- Example:
581+--
582+-- > type MyApi = "books" :> QueryParam "author" Text :> Get '[JSON] [Book]
583+-- >
584+-- > server :: Server MyApi
585+-- > server = getBooksBy
586+-- > where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book]
587+-- > getBooksBy Nothing = ...return all books...
588+-- > getBooksBy (Just author) = ...return books by the given author...
589+instance (KnownSymbol sym, FromText a, HasServer sublayout)
590+ => HasServer (QueryParam sym a :> sublayout) where
591+
592+ type ServerT' (QueryParam sym a :> sublayout) m =
593+ Maybe a -> ServerT' sublayout m
594+
595+ route Proxy subserver request respond = do
596+ let querytext = parseQueryText $ rawQueryString request
597+ param =
598+ case lookup paramname querytext of
599+ Nothing -> Nothing -- param absent from the query string
600+ Just Nothing -> Nothing -- param present with no value -> Nothing
601+ Just (Just v) -> fromText v -- if present, we try to convert to
602+ -- the right type
603+
604+ route (Proxy :: Proxy sublayout) (subserver param) request respond
605+
606+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
607+
608+-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
609+-- this automatically requires your server-side handler to be a function
610+-- that takes an argument of type @['Text']@.
611+--
612+-- This lets servant worry about looking up 0 or more values in the query string
613+-- associated to @authors@ and turning each of them into a value of
614+-- the type you specify.
615+--
616+-- You can control how the individual values are converted from 'Text' to your type
617+-- by simply providing an instance of 'FromText' for your type.
618+--
619+-- Example:
620+--
621+-- > type MyApi = "books" :> QueryParams "authors" Text :> Get '[JSON] [Book]
622+-- >
623+-- > server :: Server MyApi
624+-- > server = getBooksBy
625+-- > where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book]
626+-- > getBooksBy authors = ...return all books by these authors...
627+instance (KnownSymbol sym, FromText a, HasServer sublayout)
628+ => HasServer (QueryParams sym a :> sublayout) where
629+
630+ type ServerT' (QueryParams sym a :> sublayout) m =
631+ [a] -> ServerT' sublayout m
632+
633+ route Proxy subserver request respond = do
634+ let querytext = parseQueryText $ rawQueryString request
635+ -- if sym is "foo", we look for query string parameters
636+ -- named "foo" or "foo[]" and call fromText on the
637+ -- corresponding values
638+ parameters = filter looksLikeParam querytext
639+ values = catMaybes $ map (convert . snd) parameters
640+
641+ route (Proxy :: Proxy sublayout) (subserver values) request respond
642+
643+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
644+ looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
645+ convert Nothing = Nothing
646+ convert (Just v) = fromText v
647+
648+-- | If you use @'QueryFlag' "published"@ in one of the endpoints for your API,
649+-- this automatically requires your server-side handler to be a function
650+-- that takes an argument of type 'Bool'.
651+--
652+-- Example:
653+--
654+-- > type MyApi = "books" :> QueryFlag "published" :> Get '[JSON] [Book]
655+-- >
656+-- > server :: Server MyApi
657+-- > server = getBooks
658+-- > where getBooks :: Bool -> EitherT (Int, String) IO [Book]
659+-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
660+instance (KnownSymbol sym, HasServer sublayout)
661+ => HasServer (QueryFlag sym :> sublayout) where
662+
663+ type ServerT' (QueryFlag sym :> sublayout) m =
664+ Bool -> ServerT' sublayout m
665+
666+ route Proxy subserver request respond = do
667+ let querytext = parseQueryText $ rawQueryString request
668+ param = case lookup paramname querytext of
669+ Just Nothing -> True -- param is there, with no value
670+ Just (Just v) -> examine v -- param with a value
671+ Nothing -> False -- param not in the query string
672+
673+ route (Proxy :: Proxy sublayout) (subserver param) request respond
674+
675+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
676+ examine v | v == "true" || v == "1" || v == "" = True
677+ | otherwise = False
678+
679+parseMatrixText :: B.ByteString -> QueryText
680+parseMatrixText = parseQueryText
681+
682+-- | If you use @'MatrixParam' "author" Text@ in one of the endpoints for your API,
683+-- this automatically requires your server-side handler to be a function
684+-- that takes an argument of type @'Maybe' 'Text'@.
685+--
686+-- This lets servant worry about looking it up in the query string
687+-- and turning it into a value of the type you specify, enclosed
688+-- in 'Maybe', because it may not be there and servant would then
689+-- hand you 'Nothing'.
690+--
691+-- You can control how it'll be converted from 'Text' to your type
692+-- by simply providing an instance of 'FromText' for your type.
693+--
694+-- Example:
695+--
696+-- > type MyApi = "books" :> MatrixParam "author" Text :> Get [Book]
697+-- >
698+-- > server :: Server MyApi
699+-- > server = getBooksBy
700+-- > where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book]
701+-- > getBooksBy Nothing = ...return all books...
702+-- > getBooksBy (Just author) = ...return books by the given author...
703+instance (KnownSymbol sym, FromText a, HasServer sublayout)
704+ => HasServer (MatrixParam sym a :> sublayout) where
705+
706+ type ServerT' (MatrixParam sym a :> sublayout) m =
707+ Maybe a -> ServerT' sublayout m
708+
709+ route Proxy subserver request respond = case parsePathInfo request of
710+ (first : _)
711+ -> do let querytext = parseMatrixText . encodeUtf8 $ T.tail first
712+ param = case lookup paramname querytext of
713+ Nothing -> Nothing -- param absent from the query string
714+ Just Nothing -> Nothing -- param present with no value -> Nothing
715+ Just (Just v) -> fromText v -- if present, we try to convert to
716+ -- the right type
717+ route (Proxy :: Proxy sublayout) (subserver param) request respond
718+ _ -> route (Proxy :: Proxy sublayout) (subserver Nothing) request respond
719+
720+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
721+
722+-- | If you use @'MatrixParams' "authors" Text@ in one of the endpoints for your API,
723+-- this automatically requires your server-side handler to be a function
724+-- that takes an argument of type @['Text']@.
725+--
726+-- This lets servant worry about looking up 0 or more values in the query string
727+-- associated to @authors@ and turning each of them into a value of
728+-- the type you specify.
729+--
730+-- You can control how the individual values are converted from 'Text' to your type
731+-- by simply providing an instance of 'FromText' for your type.
732+--
733+-- Example:
734+--
735+-- > type MyApi = "books" :> MatrixParams "authors" Text :> Get [Book]
736+-- >
737+-- > server :: Server MyApi
738+-- > server = getBooksBy
739+-- > where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book]
740+-- > getBooksBy authors = ...return all books by these authors...
741+instance (KnownSymbol sym, FromText a, HasServer sublayout)
742+ => HasServer (MatrixParams sym a :> sublayout) where
743+
744+ type ServerT' (MatrixParams sym a :> sublayout) m =
745+ [a] -> ServerT' sublayout m
746+
747+ route Proxy subserver request respond = case parsePathInfo request of
748+ (first : _)
749+ -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
750+ -- if sym is "foo", we look for matrix parameters
751+ -- named "foo" or "foo[]" and call fromText on the
752+ -- corresponding values
753+ parameters = filter looksLikeParam matrixtext
754+ values = catMaybes $ map (convert . snd) parameters
755+ route (Proxy :: Proxy sublayout) (subserver values) request respond
756+ _ -> route (Proxy :: Proxy sublayout) (subserver []) request respond
757+
758+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
759+ looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
760+ convert Nothing = Nothing
761+ convert (Just v) = fromText v
762+
763+-- | If you use @'MatrixFlag' "published"@ in one of the endpoints for your API,
764+-- this automatically requires your server-side handler to be a function
765+-- that takes an argument of type 'Bool'.
766+--
767+-- Example:
768+--
769+-- > type MyApi = "books" :> MatrixFlag "published" :> Get [Book]
770+-- >
771+-- > server :: Server MyApi
772+-- > server = getBooks
773+-- > where getBooks :: Bool -> EitherT (Int, String) IO [Book]
774+-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
775+instance (KnownSymbol sym, HasServer sublayout)
776+ => HasServer (MatrixFlag sym :> sublayout) where
777+
778+ type ServerT' (MatrixFlag sym :> sublayout) m =
779+ Bool -> ServerT' sublayout m
780+
781+ route Proxy subserver request respond = case parsePathInfo request of
782+ (first : _)
783+ -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
784+ param = case lookup paramname matrixtext of
785+ Just Nothing -> True -- param is there, with no value
786+ Just (Just v) -> examine v -- param with a value
787+ Nothing -> False -- param not in the query string
788+
789+ route (Proxy :: Proxy sublayout) (subserver param) request respond
790+
791+ _ -> route (Proxy :: Proxy sublayout) (subserver False) request respond
792+
793+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
794+ examine v | v == "true" || v == "1" || v == "" = True
795+ | otherwise = False
796+
797+-- | Just pass the request to the underlying application and serve its response.
798+--
799+-- Example:
800+--
801+-- > type MyApi = "images" :> Raw
802+-- >
803+-- > server :: Server MyApi
804+-- > server = serveDirectory "/var/www/images"
805+instance HasServer Raw where
806+
807+ type ServerT' Raw m = Application
808+
809+ route Proxy rawApplication request respond =
810+ rawApplication request (respond . succeedWith)
811+
812+-- | If you use 'ReqBody' in one of the endpoints for your API,
813+-- this automatically requires your server-side handler to be a function
814+-- that takes an argument of the type specified by 'ReqBody'.
815+-- The @Content-Type@ header is inspected, and the list provided is used to
816+-- attempt deserialization. If the request does not have a @Content-Type@
817+-- header, it is treated as @application/octet-stream@.
818+-- This lets servant worry about extracting it from the request and turning
819+-- it into a value of the type you specify.
820+--
821+--
822+-- All it asks is for a 'FromJSON' instance.
823+--
824+-- Example:
825+--
826+-- > type MyApi = "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book
827+-- >
828+-- > server :: Server MyApi
829+-- > server = postBook
830+-- > where postBook :: Book -> EitherT (Int, String) IO Book
831+-- > postBook book = ...insert into your db...
832+instance ( AllCTUnrender list a, HasServer sublayout
833+ ) => HasServer (ReqBody list a :> sublayout) where
834+
835+ type ServerT' (ReqBody list a :> sublayout) m =
836+ a -> ServerT' sublayout m
837+
838+ route Proxy subserver request respond = do
839+ -- See HTTP RFC 2616, section 7.2.1
840+ -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
841+ -- See also "W3C Internet Media Type registration, consistency of use"
842+ -- http://www.w3.org/2001/tag/2002/0129-mime
843+ let contentTypeH = fromMaybe "application/octet-stream"
844+ $ lookup hContentType $ requestHeaders request
845+ mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
846+ <$> lazyRequestBody request
847+ case mrqbody of
848+ Nothing -> respond . failWith $ UnsupportedMediaType
849+ Just (Left e) -> respond . failWith $ InvalidBody e
850+ Just (Right v) -> route (Proxy :: Proxy sublayout) (subserver v) request respond
851+
852+-- | Make sure the incoming request starts with @"/path"@, strip it and
853+-- pass the rest of the request path to @sublayout@.
854+instance (KnownSymbol path, HasServer sublayout) => HasServer (path :> sublayout) where
855+
856+ type ServerT' (path :> sublayout) m = ServerT' sublayout m
857+
858+ route Proxy subserver request respond = case processedPathInfo request of
859+ (first : rest)
860+ | first == cs (symbolVal proxyPath)
861+ -> route (Proxy :: Proxy sublayout) subserver request{
862+ pathInfo = rest
863+ } respond
864+ _ -> respond $ failWith NotFound
865+
866+ where proxyPath = Proxy :: Proxy path
servant-server/src/Servant/Utils/StaticFiles.hsnew file+36
@@ -0,0 +1,36 @@
1+-- | This module defines a sever-side handler that lets you serve static files.
2+--
3+-- - 'serveDirectory' lets you serve anything that lives under a particular
4+-- directory on your filesystem.
5+module Servant.Utils.StaticFiles (
6+ serveDirectory,
7+ ) where
8+
9+import Filesystem.Path.CurrentOS (decodeString)
10+import Network.Wai.Application.Static (staticApp, defaultFileServerSettings)
11+import Servant.API.Raw (Raw)
12+import Servant.Server (Server)
13+
14+-- | Serve anything under the specified directory as a 'Raw' endpoint.
15+--
16+-- @
17+-- type MyApi = "static" :> Raw
18+--
19+-- server :: Server MyApi
20+-- server = serveDirectory "\/var\/www"
21+-- @
22+--
23+-- would capture any request to @\/static\/\<something>@ and look for
24+-- @\<something>@ under @\/var\/www@.
25+--
26+-- It will do its best to guess the MIME type for that file, based on the extension,
27+-- and send an appropriate /Content-Type/ header if possible.
28+--
29+-- If your goal is to serve HTML, CSS and Javascript files that use the rest of the API
30+-- as a webapp backend, you will most likely not want the static files to be hidden
31+-- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectory'
32+-- handler in the last position, because /servant/ will try to match the handlers
33+-- in order.
34+serveDirectory :: FilePath -> Server Raw
35+serveDirectory documentRoot =
36+ staticApp (defaultFileServerSettings (decodeString (documentRoot ++ "/")))
servant-server/test/Servant/ServerSpec.hsnew file+619
@@ -0,0 +1,619 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE TypeOperators #-}
6+{-# LANGUAGE TypeSynonymInstances #-}
7+{-# LANGUAGE FlexibleInstances #-}
8+
9+module Servant.ServerSpec where
10+
11+
12+import Control.Monad (forM_, when)
13+import Control.Monad.Trans.Either (EitherT, left)
14+import Data.Aeson (FromJSON, ToJSON, decode', encode)
15+import Data.ByteString.Conversion ()
16+import Data.Char (toUpper)
17+import Data.Monoid ((<>))
18+import Data.Proxy (Proxy (Proxy))
19+import Data.String (fromString)
20+import Data.String.Conversions (cs)
21+import qualified Data.Text as T
22+import GHC.Generics (Generic)
23+import Network.HTTP.Types (hAccept, hContentType,
24+ methodDelete, methodGet,
25+ methodPatch, methodPost, methodPut,
26+ ok200, parseQuery, status409)
27+import Network.Wai (Application, Request, pathInfo,
28+ queryString, rawQueryString,
29+ responseLBS)
30+import Network.Wai.Test (defaultRequest, request,
31+ runSession, simpleBody)
32+import Test.Hspec (Spec, describe, it, shouldBe)
33+import Test.Hspec.Wai (get, liftIO, matchHeaders,
34+ matchStatus, post, request,
35+ shouldRespondWith, with, (<:>))
36+
37+import Servant.API ((:<|>) (..), (:>),
38+ AddHeader (addHeader), Capture,
39+ Delete, Get, Header (..), Headers,
40+ JSON, MatrixFlag, MatrixParam,
41+ MatrixParams, Patch, PlainText,
42+ Post, Put, QueryFlag, QueryParam,
43+ QueryParams, Raw, ReqBody)
44+import Servant.Server (Server, serve)
45+import Servant.Server.Internal (RouteMismatch (..))
46+
47+
48+-- * test data types
49+
50+data Person = Person {
51+ name :: String,
52+ age :: Integer
53+ }
54+ deriving (Eq, Show, Generic)
55+
56+instance ToJSON Person
57+instance FromJSON Person
58+
59+alice :: Person
60+alice = Person "Alice" 42
61+
62+data Animal = Animal {
63+ species :: String,
64+ numberOfLegs :: Integer
65+ }
66+ deriving (Eq, Show, Generic)
67+
68+instance ToJSON Animal
69+instance FromJSON Animal
70+
71+jerry :: Animal
72+jerry = Animal "Mouse" 4
73+
74+tweety :: Animal
75+tweety = Animal "Bird" 2
76+
77+
78+-- * specs
79+
80+spec :: Spec
81+spec = do
82+ captureSpec
83+ getSpec
84+ postSpec
85+ putSpec
86+ patchSpec
87+ queryParamSpec
88+ matrixParamSpec
89+ headerSpec
90+ rawSpec
91+ unionSpec
92+ errorsSpec
93+ responseHeadersSpec
94+
95+
96+type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal
97+captureApi :: Proxy CaptureApi
98+captureApi = Proxy
99+captureServer :: Integer -> EitherT (Int, String) IO Animal
100+captureServer legs = case legs of
101+ 4 -> return jerry
102+ 2 -> return tweety
103+ _ -> left (404, "not found")
104+
105+captureSpec :: Spec
106+captureSpec = do
107+ describe "Servant.API.Capture" $ do
108+ with (return (serve captureApi captureServer)) $ do
109+
110+ it "can capture parts of the 'pathInfo'" $ do
111+ response <- get "/2"
112+ liftIO $ decode' (simpleBody response) `shouldBe` Just tweety
113+
114+ it "returns 404 if the decoding fails" $ do
115+ get "/notAnInt" `shouldRespondWith` 404
116+
117+ with (return (serve
118+ (Proxy :: Proxy (Capture "captured" String :> Raw))
119+ (\ "captured" request_ respond ->
120+ respond $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do
121+ it "strips the captured path snippet from pathInfo" $ do
122+ get "/captured/foo" `shouldRespondWith` (fromString (show ["foo" :: String]))
123+
124+
125+type GetApi = Get '[JSON] Person
126+ :<|> "empty" :> Get '[] ()
127+getApi :: Proxy GetApi
128+getApi = Proxy
129+
130+getSpec :: Spec
131+getSpec = do
132+ describe "Servant.API.Get" $ do
133+ let server = return alice :<|> return ()
134+ with (return $ serve getApi server) $ do
135+
136+ it "allows to GET a Person" $ do
137+ response <- get "/"
138+ return response `shouldRespondWith` 200
139+ liftIO $ decode' (simpleBody response) `shouldBe` Just alice
140+
141+ it "throws 405 (wrong method) on POSTs" $ do
142+ post "/" "" `shouldRespondWith` 405
143+ post "/empty" "" `shouldRespondWith` 405
144+
145+ it "returns 204 if the type is '()'" $ do
146+ get "empty" `shouldRespondWith` ""{ matchStatus = 204 }
147+
148+ it "returns 415 if the Accept header is not supported" $ do
149+ Test.Hspec.Wai.request methodGet "" [(hAccept, "crazy/mime")] ""
150+ `shouldRespondWith` 415
151+
152+
153+
154+type QueryParamApi = QueryParam "name" String :> Get '[JSON] Person
155+ :<|> "a" :> QueryParams "names" String :> Get '[JSON] Person
156+ :<|> "b" :> QueryFlag "capitalize" :> Get '[JSON] Person
157+
158+queryParamApi :: Proxy QueryParamApi
159+queryParamApi = Proxy
160+
161+qpServer :: Server QueryParamApi
162+qpServer = queryParamServer :<|> qpNames :<|> qpCapitalize
163+
164+ where qpNames (_:name2:_) = return alice { name = name2 }
165+ qpNames _ = return alice
166+
167+ qpCapitalize False = return alice
168+ qpCapitalize True = return alice { name = map toUpper (name alice) }
169+
170+ queryParamServer (Just name_) = return alice{name = name_}
171+ queryParamServer Nothing = return alice
172+
173+queryParamSpec :: Spec
174+queryParamSpec = do
175+ describe "Servant.API.QueryParam" $ do
176+ it "allows to retrieve simple GET parameters" $
177+ (flip runSession) (serve queryParamApi qpServer) $ do
178+ let params1 = "?name=bob"
179+ response1 <- Network.Wai.Test.request defaultRequest{
180+ rawQueryString = params1,
181+ queryString = parseQuery params1
182+ }
183+ liftIO $ do
184+ decode' (simpleBody response1) `shouldBe` Just alice{
185+ name = "bob"
186+ }
187+
188+ it "allows to retrieve lists in GET parameters" $
189+ (flip runSession) (serve queryParamApi qpServer) $ do
190+ let params2 = "?names[]=bob&names[]=john"
191+ response2 <- Network.Wai.Test.request defaultRequest{
192+ rawQueryString = params2,
193+ queryString = parseQuery params2,
194+ pathInfo = ["a"]
195+ }
196+ liftIO $
197+ decode' (simpleBody response2) `shouldBe` Just alice{
198+ name = "john"
199+ }
200+
201+
202+ it "allows to retrieve value-less GET parameters" $
203+ (flip runSession) (serve queryParamApi qpServer) $ do
204+ let params3 = "?capitalize"
205+ response3 <- Network.Wai.Test.request defaultRequest{
206+ rawQueryString = params3,
207+ queryString = parseQuery params3,
208+ pathInfo = ["b"]
209+ }
210+ liftIO $
211+ decode' (simpleBody response3) `shouldBe` Just alice{
212+ name = "ALICE"
213+ }
214+
215+ let params3' = "?capitalize="
216+ response3' <- Network.Wai.Test.request defaultRequest{
217+ rawQueryString = params3',
218+ queryString = parseQuery params3',
219+ pathInfo = ["b"]
220+ }
221+ liftIO $
222+ decode' (simpleBody response3') `shouldBe` Just alice{
223+ name = "ALICE"
224+ }
225+
226+ let params3'' = "?unknown="
227+ response3' <- Network.Wai.Test.request defaultRequest{
228+ rawQueryString = params3'',
229+ queryString = parseQuery params3'',
230+ pathInfo = ["b"]
231+ }
232+ liftIO $
233+ decode' (simpleBody response3') `shouldBe` Just alice{
234+ name = "Alice"
235+ }
236+
237+type MatrixParamApi = "a" :> MatrixParam "name" String :> Get '[JSON] Person
238+ :<|> "b" :> MatrixParams "names" String :> "bsub" :> MatrixParams "names" String :> Get '[JSON] Person
239+ :<|> "c" :> MatrixFlag "capitalize" :> Get '[JSON] Person
240+ :<|> "d" :> Capture "foo" Integer :> MatrixParam "name" String :> MatrixFlag "capitalize" :> "dsub" :> Get '[JSON] Person
241+
242+matrixParamApi :: Proxy MatrixParamApi
243+matrixParamApi = Proxy
244+
245+mpServer :: Server MatrixParamApi
246+mpServer = matrixParamServer :<|> mpNames :<|> mpCapitalize alice :<|> mpComplex
247+ where mpNames (_:name2:_) _ = return alice { name = name2 }
248+ mpNames _ _ = return alice
249+
250+ mpCapitalize p False = return p
251+ mpCapitalize p True = return p { name = map toUpper (name p) }
252+
253+ matrixParamServer (Just name) = return alice{name = name}
254+ matrixParamServer Nothing = return alice
255+
256+ mpAge age p = return p { age = age }
257+ mpComplex capture name cap = matrixParamServer name >>= flip mpCapitalize cap >>= mpAge capture
258+
259+matrixParamSpec :: Spec
260+matrixParamSpec = do
261+ describe "Servant.API.MatrixParam" $ do
262+ it "allows to retrieve simple matrix parameters" $
263+ (flip runSession) (serve matrixParamApi mpServer) $ do
264+ response1 <- Network.Wai.Test.request defaultRequest{
265+ pathInfo = ["a;name=bob"]
266+ }
267+ liftIO $ do
268+ decode' (simpleBody response1) `shouldBe` Just alice{
269+ name = "bob"
270+ }
271+
272+ it "allows to retrieve lists in matrix parameters" $
273+ (flip runSession) (serve matrixParamApi mpServer) $ do
274+ response2 <- Network.Wai.Test.request defaultRequest{
275+ pathInfo = ["b;names=bob;names=john", "bsub;names=anna;names=sarah"]
276+ }
277+ liftIO $
278+ decode' (simpleBody response2) `shouldBe` Just alice{
279+ name = "john"
280+ }
281+
282+ it "allows to retrieve value-less matrix parameters" $
283+ (flip runSession) (serve matrixParamApi mpServer) $ do
284+ response3 <- Network.Wai.Test.request defaultRequest{
285+ pathInfo = ["c;capitalize"]
286+ }
287+ liftIO $
288+ decode' (simpleBody response3) `shouldBe` Just alice{
289+ name = "ALICE"
290+ }
291+
292+ response3' <- Network.Wai.Test.request defaultRequest{
293+ pathInfo = ["c;capitalize="]
294+ }
295+ liftIO $
296+ decode' (simpleBody response3') `shouldBe` Just alice{
297+ name = "ALICE"
298+ }
299+
300+ it "allows to retrieve matrix parameters on captured segments" $
301+ (flip runSession) (serve matrixParamApi mpServer) $ do
302+ response4 <- Network.Wai.Test.request defaultRequest{
303+ pathInfo = ["d", "12;name=stephen;capitalize", "dsub"]
304+ }
305+ liftIO $
306+ decode' (simpleBody response4) `shouldBe` Just alice{
307+ name = "STEPHEN",
308+ age = 12
309+ }
310+
311+ response4' <- Network.Wai.Test.request defaultRequest{
312+ pathInfo = ["d;ignored=1", "5", "dsub"]
313+ }
314+ liftIO $
315+ decode' (simpleBody response4') `shouldBe` Just alice{
316+ name = "Alice",
317+ age = 5
318+ }
319+
320+type PostApi =
321+ ReqBody '[JSON] Person :> Post '[JSON] Integer
322+ :<|> "bla" :> ReqBody '[JSON] Person :> Post '[JSON] Integer
323+ :<|> "empty" :> Post '[] ()
324+
325+postApi :: Proxy PostApi
326+postApi = Proxy
327+
328+postSpec :: Spec
329+postSpec = do
330+ describe "Servant.API.Post and .ReqBody" $ do
331+ let server = return . age :<|> return . age :<|> return ()
332+ with (return $ serve postApi server) $ do
333+ let post' x = Test.Hspec.Wai.request methodPost x [(hContentType
334+ , "application/json;charset=utf-8")]
335+
336+ it "allows to POST a Person" $ do
337+ post' "/" (encode alice) `shouldRespondWith` "42"{
338+ matchStatus = 201
339+ }
340+
341+ it "allows alternative routes if all have request bodies" $ do
342+ post' "/bla" (encode alice) `shouldRespondWith` "42"{
343+ matchStatus = 201
344+ }
345+
346+ it "handles trailing '/' gracefully" $ do
347+ post' "/bla/" (encode alice) `shouldRespondWith` "42"{
348+ matchStatus = 201
349+ }
350+
351+ it "correctly rejects invalid request bodies with status 400" $ do
352+ post' "/" "some invalid body" `shouldRespondWith` 400
353+
354+ it "returns 204 if the type is '()'" $ do
355+ post' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
356+
357+ it "responds with 415 if the requested media type is unsupported" $ do
358+ let post'' x = Test.Hspec.Wai.request methodPost x [(hContentType
359+ , "application/nonsense")]
360+ post'' "/" "anything at all" `shouldRespondWith` 415
361+
362+type PutApi =
363+ ReqBody '[JSON] Person :> Put '[JSON] Integer
364+ :<|> "bla" :> ReqBody '[JSON] Person :> Put '[JSON] Integer
365+ :<|> "empty" :> Put '[] ()
366+
367+putApi :: Proxy PutApi
368+putApi = Proxy
369+
370+putSpec :: Spec
371+putSpec = do
372+ describe "Servant.API.Put and .ReqBody" $ do
373+ let server = return . age :<|> return . age :<|> return ()
374+ with (return $ serve putApi server) $ do
375+ let put' x = Test.Hspec.Wai.request methodPut x [(hContentType
376+ , "application/json;charset=utf-8")]
377+
378+ it "allows to put a Person" $ do
379+ put' "/" (encode alice) `shouldRespondWith` "42"{
380+ matchStatus = 200
381+ }
382+
383+ it "allows alternative routes if all have request bodies" $ do
384+ put' "/bla" (encode alice) `shouldRespondWith` "42"{
385+ matchStatus = 200
386+ }
387+
388+ it "handles trailing '/' gracefully" $ do
389+ put' "/bla/" (encode alice) `shouldRespondWith` "42"{
390+ matchStatus = 200
391+ }
392+
393+ it "correctly rejects invalid request bodies with status 400" $ do
394+ put' "/" "some invalid body" `shouldRespondWith` 400
395+
396+ it "returns 204 if the type is '()'" $ do
397+ put' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
398+
399+ it "responds with 415 if the requested media type is unsupported" $ do
400+ let put'' x = Test.Hspec.Wai.request methodPut x [(hContentType
401+ , "application/nonsense")]
402+ put'' "/" "anything at all" `shouldRespondWith` 415
403+
404+type PatchApi =
405+ ReqBody '[JSON] Person :> Patch '[JSON] Integer
406+ :<|> "bla" :> ReqBody '[JSON] Person :> Patch '[JSON] Integer
407+ :<|> "empty" :> Patch '[] ()
408+
409+patchApi :: Proxy PatchApi
410+patchApi = Proxy
411+
412+patchSpec :: Spec
413+patchSpec = do
414+ describe "Servant.API.Patch and .ReqBody" $ do
415+ let server = return . age :<|> return . age :<|> return ()
416+ with (return $ serve patchApi server) $ do
417+ let patch' x = Test.Hspec.Wai.request methodPatch x [(hContentType
418+ , "application/json;charset=utf-8")]
419+
420+ it "allows to patch a Person" $ do
421+ patch' "/" (encode alice) `shouldRespondWith` "42"{
422+ matchStatus = 200
423+ }
424+
425+ it "allows alternative routes if all have request bodies" $ do
426+ patch' "/bla" (encode alice) `shouldRespondWith` "42"{
427+ matchStatus = 200
428+ }
429+
430+ it "handles trailing '/' gracefully" $ do
431+ patch' "/bla/" (encode alice) `shouldRespondWith` "42"{
432+ matchStatus = 200
433+ }
434+
435+ it "correctly rejects invalid request bodies with status 400" $ do
436+ patch' "/" "some invalid body" `shouldRespondWith` 400
437+
438+ it "returns 204 if the type is '()'" $ do
439+ patch' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
440+
441+ it "responds with 415 if the requested media type is unsupported" $ do
442+ let patch'' x = Test.Hspec.Wai.request methodPatch x [(hContentType
443+ , "application/nonsense")]
444+ patch'' "/" "anything at all" `shouldRespondWith` 415
445+
446+type HeaderApi a = Header "MyHeader" a :> Delete
447+headerApi :: Proxy (HeaderApi a)
448+headerApi = Proxy
449+
450+headerSpec :: Spec
451+headerSpec = describe "Servant.API.Header" $ do
452+
453+ let expectsInt :: Maybe Int -> EitherT (Int,String) IO ()
454+ expectsInt (Just x) = when (x /= 5) $ error "Expected 5"
455+ expectsInt Nothing = error "Expected an int"
456+
457+ let expectsString :: Maybe String -> EitherT (Int,String) IO ()
458+ expectsString (Just x) = when (x /= "more from you") $ error "Expected more from you"
459+ expectsString Nothing = error "Expected a string"
460+
461+ with (return (serve headerApi expectsInt)) $ do
462+ let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader" ,"5")]
463+
464+ it "passes the header to the handler (Int)" $
465+ delete' "/" "" `shouldRespondWith` 204
466+
467+ with (return (serve headerApi expectsString)) $ do
468+ let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader" ,"more from you")]
469+
470+ it "passes the header to the handler (String)" $
471+ delete' "/" "" `shouldRespondWith` 204
472+
473+
474+type RawApi = "foo" :> Raw
475+rawApi :: Proxy RawApi
476+rawApi = Proxy
477+rawApplication :: Show a => (Request -> a) -> Application
478+rawApplication f request_ respond = respond $ responseLBS ok200 [] (cs $ show $ f request_)
479+
480+rawSpec :: Spec
481+rawSpec = do
482+ describe "Servant.API.Raw" $ do
483+ it "runs applications" $ do
484+ (flip runSession) (serve rawApi (rawApplication (const (42 :: Integer)))) $ do
485+ response <- Network.Wai.Test.request defaultRequest{
486+ pathInfo = ["foo"]
487+ }
488+ liftIO $ do
489+ simpleBody response `shouldBe` "42"
490+
491+ it "gets the pathInfo modified" $ do
492+ (flip runSession) (serve rawApi (rawApplication pathInfo)) $ do
493+ response <- Network.Wai.Test.request defaultRequest{
494+ pathInfo = ["foo", "bar"]
495+ }
496+ liftIO $ do
497+ simpleBody response `shouldBe` cs (show ["bar" :: String])
498+
499+
500+type AlternativeApi =
501+ "foo" :> Get '[JSON] Person
502+ :<|> "bar" :> Get '[JSON] Animal
503+ :<|> "foo" :> Get '[PlainText] T.Text
504+ :<|> "bar" :> Post '[JSON] Animal
505+ :<|> "bar" :> Put '[JSON] Animal
506+ :<|> "bar" :> Delete
507+unionApi :: Proxy AlternativeApi
508+unionApi = Proxy
509+
510+unionServer :: Server AlternativeApi
511+unionServer =
512+ return alice
513+ :<|> return jerry
514+ :<|> return "a string"
515+ :<|> return jerry
516+ :<|> return jerry
517+ :<|> return ()
518+
519+unionSpec :: Spec
520+unionSpec = do
521+ describe "Servant.API.Alternative" $ do
522+ with (return $ serve unionApi unionServer) $ do
523+
524+ it "unions endpoints" $ do
525+ response <- get "/foo"
526+ liftIO $ do
527+ decode' (simpleBody response) `shouldBe`
528+ Just alice
529+ response_ <- get "/bar"
530+ liftIO $ do
531+ decode' (simpleBody response_) `shouldBe`
532+ Just jerry
533+
534+ it "checks all endpoints before returning 415" $ do
535+ get "/foo" `shouldRespondWith` 200
536+
537+ it "returns 404 if the path does not exist" $ do
538+ get "/nonexistent" `shouldRespondWith` 404
539+
540+type ResponseHeadersApi =
541+ Get '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
542+ :<|> Post '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
543+ :<|> Put '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
544+ :<|> Patch '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
545+
546+
547+responseHeadersServer :: Server ResponseHeadersApi
548+responseHeadersServer = let h = return $ addHeader 5 $ addHeader "kilroy" "hi"
549+ in h :<|> h :<|> h :<|> h
550+
551+
552+responseHeadersSpec :: Spec
553+responseHeadersSpec = describe "ResponseHeaders" $ do
554+ with (return $ serve (Proxy :: Proxy ResponseHeadersApi) responseHeadersServer) $ do
555+
556+ let methods = [(methodGet, 200), (methodPost, 201), (methodPut, 200), (methodPatch, 200)]
557+
558+ it "includes the headers in the response" $
559+ forM_ methods $ \(method, expected) ->
560+ Test.Hspec.Wai.request method "/" [] ""
561+ `shouldRespondWith` "\"hi\""{ matchHeaders = ["H1" <:> "5", "H2" <:> "kilroy"]
562+ , matchStatus = expected
563+ }
564+
565+ it "responds with not found for non-existent endpoints" $
566+ forM_ methods $ \(method,_) ->
567+ Test.Hspec.Wai.request method "blahblah" [] ""
568+ `shouldRespondWith` 404
569+
570+ it "returns 415 if the Accept header is not supported" $
571+ forM_ methods $ \(method,_) ->
572+ Test.Hspec.Wai.request method "" [(hAccept, "crazy/mime")] ""
573+ `shouldRespondWith` 415
574+
575+
576+-- | Test server error functionality.
577+errorsSpec :: Spec
578+errorsSpec = do
579+ let he = HttpError status409 (Just "A custom error")
580+ let ib = InvalidBody "The body is invalid"
581+ let wm = WrongMethod
582+ let nf = NotFound
583+
584+ describe "Servant.Server.Internal.RouteMismatch" $ do
585+ it "HttpError > *" $ do
586+ ib <> he `shouldBe` he
587+ wm <> he `shouldBe` he
588+ nf <> he `shouldBe` he
589+
590+ he <> ib `shouldBe` he
591+ he <> wm `shouldBe` he
592+ he <> nf `shouldBe` he
593+
594+ it "HE > InvalidBody > (WM,NF)" $ do
595+ he <> ib `shouldBe` he
596+ wm <> ib `shouldBe` ib
597+ nf <> ib `shouldBe` ib
598+
599+ ib <> he `shouldBe` he
600+ ib <> wm `shouldBe` ib
601+ ib <> nf `shouldBe` ib
602+
603+ it "HE > IB > WrongMethod > NF" $ do
604+ he <> wm `shouldBe` he
605+ ib <> wm `shouldBe` ib
606+ nf <> wm `shouldBe` wm
607+
608+ wm <> he `shouldBe` he
609+ wm <> ib `shouldBe` ib
610+ wm <> nf `shouldBe` wm
611+
612+ it "* > NotFound" $ do
613+ he <> nf `shouldBe` he
614+ ib <> nf `shouldBe` ib
615+ wm <> nf `shouldBe` wm
616+
617+ nf <> he `shouldBe` he
618+ nf <> ib `shouldBe` ib
619+ nf <> wm `shouldBe` wm
servant-server/test/Servant/Utils/StaticFilesSpec.hsnew file+65
@@ -0,0 +1,65 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE TypeOperators #-}
5+{-# OPTIONS_GHC -fno-warn-orphans #-}
6+module Servant.Utils.StaticFilesSpec where
7+
8+import Control.Exception (bracket)
9+import Data.Proxy (Proxy(Proxy))
10+import Network.Wai (Application)
11+import System.Directory (getCurrentDirectory, setCurrentDirectory, createDirectory)
12+import System.IO.Temp (withSystemTempDirectory)
13+import Test.Hspec (Spec, describe, it, around_)
14+import Test.Hspec.Wai (with, get, shouldRespondWith)
15+
16+import Servant.API (JSON)
17+import Servant.API.Alternative ((:<|>)((:<|>)))
18+import Servant.API.Capture (Capture)
19+import Servant.API.Get (Get)
20+import Servant.API.Raw (Raw)
21+import Servant.API.Sub ((:>))
22+import Servant.Server (Server, serve)
23+import Servant.ServerSpec (Person(Person))
24+import Servant.Utils.StaticFiles (serveDirectory)
25+
26+type Api =
27+ "dummy_api" :> Capture "person_name" String :> Get '[JSON] Person
28+ :<|> "static" :> Raw
29+
30+
31+api :: Proxy Api
32+api = Proxy
33+
34+app :: Application
35+app = serve api server
36+
37+server :: Server Api
38+server =
39+ (\ name_ -> return (Person name_ 42))
40+ :<|> serveDirectory "static"
41+
42+withStaticFiles :: IO () -> IO ()
43+withStaticFiles action = withSystemTempDirectory "servant-test" $ \ tmpDir ->
44+ bracket (setup tmpDir) teardown (const action)
45+ where
46+ setup tmpDir = do
47+ outer <- getCurrentDirectory
48+ setCurrentDirectory tmpDir
49+ createDirectory "static"
50+ writeFile "static/foo.txt" "bar"
51+ writeFile "static/index.html" "index"
52+ return outer
53+
54+ teardown outer = do
55+ setCurrentDirectory outer
56+
57+spec :: Spec
58+spec = do
59+ around_ withStaticFiles $ with (return app) $ do
60+ describe "serveDirectory" $ do
61+ it "successfully serves files" $ do
62+ get "/static/foo.txt" `shouldRespondWith` "bar"
63+
64+ it "serves the contents of index.html when requesting the root of a directory" $ do
65+ get "/static/" `shouldRespondWith` "index"
servant-server/test/Spec.hsnew file+1
@@ -0,0 +1 @@
1+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
CHANGELOG.md → servant/CHANGELOG.mdrenamed

The content is the same.

servant/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2014, Zalora South East Asia Pte Ltd
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Zalora South East Asia Pte Ltd nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.md → servant/README.mdrenamed

The content is the same.

servant/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
default.nix → servant/default.nixrenamed

The content is the same.

docs.sh → servant/docs.shrenamed

The content is the same.

servant.cabal → servant/servant.cabalrenamed

The content is the same.

servant.png → servant/servant.pngrenamed

Binary file not shown.

src/Servant/API.hs → servant/src/Servant/API.hsrenamed

The content is the same.

src/Servant/API/Alternative.hs → servant/src/Servant/API/Alternative.hsrenamed

The content is the same.

src/Servant/API/Capture.hs → servant/src/Servant/API/Capture.hsrenamed

The content is the same.

src/Servant/API/ContentTypes.hs → servant/src/Servant/API/ContentTypes.hsrenamed

The content is the same.

src/Servant/API/Delete.hs → servant/src/Servant/API/Delete.hsrenamed

The content is the same.

src/Servant/API/Get.hs → servant/src/Servant/API/Get.hsrenamed

The content is the same.

src/Servant/API/Header.hs → servant/src/Servant/API/Header.hsrenamed

The content is the same.

src/Servant/API/MatrixParam.hs → servant/src/Servant/API/MatrixParam.hsrenamed

The content is the same.

src/Servant/API/Patch.hs → servant/src/Servant/API/Patch.hsrenamed

The content is the same.

src/Servant/API/Post.hs → servant/src/Servant/API/Post.hsrenamed

The content is the same.

src/Servant/API/Put.hs → servant/src/Servant/API/Put.hsrenamed

The content is the same.

src/Servant/API/QueryParam.hs → servant/src/Servant/API/QueryParam.hsrenamed

The content is the same.

src/Servant/API/Raw.hs → servant/src/Servant/API/Raw.hsrenamed

The content is the same.

src/Servant/API/ReqBody.hs → servant/src/Servant/API/ReqBody.hsrenamed

The content is the same.

src/Servant/API/ResponseHeaders.hs → servant/src/Servant/API/ResponseHeaders.hsrenamed

The content is the same.

src/Servant/API/Sub.hs → servant/src/Servant/API/Sub.hsrenamed

The content is the same.

src/Servant/Common/Text.hs → servant/src/Servant/Common/Text.hsrenamed

The content is the same.

src/Servant/Utils/Links.hs → servant/src/Servant/Utils/Links.hsrenamed

The content is the same.

test/Doctests.hs → servant/test/Doctests.hsrenamed

The content is the same.

test/Servant/API/ContentTypesSpec.hs → servant/test/Servant/API/ContentTypesSpec.hsrenamed

The content is the same.

test/Servant/API/ResponseHeadersSpec.hs → servant/test/Servant/API/ResponseHeadersSpec.hsrenamed

The content is the same.

test/Servant/Common/TextSpec.hs → servant/test/Servant/Common/TextSpec.hsrenamed

The content is the same.

test/Servant/Utils/LinksSpec.hs → servant/test/Servant/Utils/LinksSpec.hsrenamed

The content is the same.

servant/test/Spec.hsnew file+1
@@ -0,0 +1 @@
1+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
update-nixpkgs.sh → servant/update-nixpkgs.shrenamed

The content is the same.