From 84b9ea271ec7c0fbff6e99ce23a65358ed6a948e Mon Sep 17 00:00:00 2001 From: Alp Mestanogullari Date: Tue, 2 Dec 2014 18:34:48 +0100 Subject: [PATCH] Built from 1defab0d2fb4457fafda0f645de24e5e7c28a95f --- .ghci | 1 - .travis.yml | 12 - Servant-API-Alternative.html | 13 + Servant-API-Capture.html | 15 + Servant-API-Delete.html | 13 + Servant-API-Get.html | 12 + Servant-API-Post.html | 17 + Servant-API-Put.html | 15 + Servant-API-QueryParam.html | 45 ++ Servant-API-Raw.html | 11 + Servant-API-ReqBody.html | 14 + Servant-API-Sub.html | 56 +++ Servant-API.html | 4 + Servant-Common-Text.html | 7 + Servant-QQ.html | 17 + Servant-Server.html | 116 ++++++ Servant-Utils-Links.html | 23 ++ Servant-Utils-StaticFiles.html | 15 + Servant.html | 6 + doc-index.html | 4 + frames.html | 30 ++ haddock-util.js | 344 ++++++++++++++++ hslogo-16.png | Bin 0 -> 1684 bytes index-frames.html | 4 + index.html | 4 + mini_Servant-API-Alternative.html | 4 + mini_Servant-API-Capture.html | 4 + mini_Servant-API-Delete.html | 4 + mini_Servant-API-Get.html | 4 + mini_Servant-API-Post.html | 4 + mini_Servant-API-Put.html | 4 + mini_Servant-API-QueryParam.html | 4 + mini_Servant-API-Raw.html | 4 + mini_Servant-API-ReqBody.html | 4 + mini_Servant-API-Sub.html | 4 + mini_Servant-API.html | 4 + mini_Servant-Common-Text.html | 4 + mini_Servant-QQ.html | 4 + mini_Servant-Server.html | 4 + mini_Servant-Utils-Links.html | 4 + mini_Servant-Utils-StaticFiles.html | 4 + mini_Servant.html | 4 + minus.gif | Bin 0 -> 56 bytes ocean.css | 577 ++++++++++++++++++++++++++ plus.gif | Bin 0 -> 59 bytes servant.haddock | Bin 0 -> 56217 bytes servant.txt | 611 ++++++++++++++++++++++++++++ src/Servant-API-Alternative.html | 50 +++ src/Servant-API-Capture.html | 71 ++++ src/Servant-API-Delete.html | 59 +++ src/Servant-API-Get.html | 58 +++ src/Servant-API-Post.html | 64 +++ src/Servant-API-Put.html | 63 +++ src/Servant-API-QueryParam.html | 173 ++++++++ src/Servant-API-Raw.html | 43 ++ src/Servant-API-ReqBody.html | 60 +++ src/Servant-API-Sub.html | 47 +++ src/Servant-API.html | 62 +++ src/Servant-Common-Text.html | 141 +++++++ src/Servant-QQ.html | 209 ++++++++++ src/Servant-Server.html | 116 ++++++ src/Servant-Utils-Links.html | 121 ++++++ src/Servant-Utils-StaticFiles.html | 47 +++ src/Servant.html | 35 ++ src/hscolour.css | 5 + synopsis.png | Bin 0 -> 11327 bytes 66 files changed, 3465 insertions(+), 13 deletions(-) delete mode 100644 .ghci delete mode 100644 .travis.yml create mode 100644 Servant-API-Alternative.html create mode 100644 Servant-API-Capture.html create mode 100644 Servant-API-Delete.html create mode 100644 Servant-API-Get.html create mode 100644 Servant-API-Post.html create mode 100644 Servant-API-Put.html create mode 100644 Servant-API-QueryParam.html create mode 100644 Servant-API-Raw.html create mode 100644 Servant-API-ReqBody.html create mode 100644 Servant-API-Sub.html create mode 100644 Servant-API.html create mode 100644 Servant-Common-Text.html create mode 100644 Servant-QQ.html create mode 100644 Servant-Server.html create mode 100644 Servant-Utils-Links.html create mode 100644 Servant-Utils-StaticFiles.html create mode 100644 Servant.html create mode 100644 doc-index.html create mode 100644 frames.html create mode 100644 haddock-util.js create mode 100644 hslogo-16.png create mode 100644 index-frames.html create mode 100644 index.html create mode 100644 mini_Servant-API-Alternative.html create mode 100644 mini_Servant-API-Capture.html create mode 100644 mini_Servant-API-Delete.html create mode 100644 mini_Servant-API-Get.html create mode 100644 mini_Servant-API-Post.html create mode 100644 mini_Servant-API-Put.html create mode 100644 mini_Servant-API-QueryParam.html create mode 100644 mini_Servant-API-Raw.html create mode 100644 mini_Servant-API-ReqBody.html create mode 100644 mini_Servant-API-Sub.html create mode 100644 mini_Servant-API.html create mode 100644 mini_Servant-Common-Text.html create mode 100644 mini_Servant-QQ.html create mode 100644 mini_Servant-Server.html create mode 100644 mini_Servant-Utils-Links.html create mode 100644 mini_Servant-Utils-StaticFiles.html create mode 100644 mini_Servant.html create mode 100644 minus.gif create mode 100644 ocean.css create mode 100644 plus.gif create mode 100644 servant.haddock create mode 100644 servant.txt create mode 100644 src/Servant-API-Alternative.html create mode 100644 src/Servant-API-Capture.html create mode 100644 src/Servant-API-Delete.html create mode 100644 src/Servant-API-Get.html create mode 100644 src/Servant-API-Post.html create mode 100644 src/Servant-API-Put.html create mode 100644 src/Servant-API-QueryParam.html create mode 100644 src/Servant-API-Raw.html create mode 100644 src/Servant-API-ReqBody.html create mode 100644 src/Servant-API-Sub.html create mode 100644 src/Servant-API.html create mode 100644 src/Servant-Common-Text.html create mode 100644 src/Servant-QQ.html create mode 100644 src/Servant-Server.html create mode 100644 src/Servant-Utils-Links.html create mode 100644 src/Servant-Utils-StaticFiles.html create mode 100644 src/Servant.html create mode 100644 src/hscolour.css create mode 100644 synopsis.png diff --git a/.ghci b/.ghci deleted file mode 100644 index 93d9b991..00000000 --- a/.ghci +++ /dev/null @@ -1 +0,0 @@ -:set -itest -isrc -packagehspec2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 03aca3f5..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: haskell - -notifications: - irc: - channels: - - "irc.freenode.org#servant" - template: - - "%{repository}#%{build_number} - %{commit} on %{branch} by %{author}: %{message}" - - "Build details: %{build_url} - Change view: %{compare_url}" - skip_join: true - on_success: change - on_failure: always diff --git a/Servant-API-Alternative.html b/Servant-API-Alternative.html new file mode 100644 index 00000000..40677d7d --- /dev/null +++ b/Servant-API-Alternative.html @@ -0,0 +1,13 @@ +Servant.API.Alternative

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Alternative

Synopsis

Documentation

data a :<|> b infixr 8 Source

Union of two APIs, first takes precedence in case of overlap.

Example:

type MyApi = "books" :> Get [Book] -- GET /books
+        :<|> "books" :> ReqBody Book :> Post Book -- POST /books

Constructors

a :<|> b infixr 8 

Instances

(HasServer a, HasServer b) => HasServer ((:<|>) a b)

A server for a :<|> b first tries to match the request again the route + represented by a and if it fails tries b. You must provide a request + handler for each route.

type MyApi = "books" :> Get [Book] -- GET /books
+        :<|> "books" :> ReqBody Book :> Post Book -- POST /books
+
+server :: Server MyApi
+server = listAllBooks :<|> postBook
+  where listAllBooks = ...
+        postBook book = ...
type Server ((:<|>) a b) = (:<|>) (Server a) (Server b) 
\ No newline at end of file diff --git a/Servant-API-Capture.html b/Servant-API-Capture.html new file mode 100644 index 00000000..30f0100e --- /dev/null +++ b/Servant-API-Capture.html @@ -0,0 +1,15 @@ +Servant.API.Capture

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Capture

Synopsis

Documentation

data Capture sym a Source

Capture a value from the request path under a certain type a.

Example:

           -- GET /books/:isbn
+type MyApi = "books" :> Capture "isbn" Text :> Get Book

Instances

(KnownSymbol capture, FromText a, HasServer sublayout) => HasServer ((:>) * (Capture Symbol * capture a) sublayout)

If you use Capture in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of the type specified by the Capture. + This lets servant worry about getting it from the URL and turning + it into a value of the type you specify.

You can control how it'll be converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> Capture "isbn" Text :> Get Book
+
+server :: Server MyApi
+server = getBook
+  where getBook :: Text -> EitherT (Int, String) IO Book
+        getBook isbn = ...
type Server ((:>) * (Capture Symbol * capture a) sublayout) = a -> Server sublayout 
\ No newline at end of file diff --git a/Servant-API-Delete.html b/Servant-API-Delete.html new file mode 100644 index 00000000..30c49f0d --- /dev/null +++ b/Servant-API-Delete.html @@ -0,0 +1,13 @@ +Servant.API.Delete

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Delete

Synopsis

Documentation

data Delete Source

Combinator for DELETE requests.

Example:

           -- DELETE /books/:isbn
+type MyApi = "books" :> Capture "isbn" Text :> Delete

Instances

HasServer Delete

If you have a Delete endpoint in your API, + the handler for this endpoint is meant to delete + a resource.

The code of the handler will, just like + for Get, Post and + Put, run in EitherT (Int, String) IO (). + The Int represents the status code and the String a message + to be returned. You can use left to + painlessly error out if the conditions for a successful deletion + are not met.

Typeable * Delete 
type Server Delete = EitherT (Int, String) IO () 
\ No newline at end of file diff --git a/Servant-API-Get.html b/Servant-API-Get.html new file mode 100644 index 00000000..3b3d294c --- /dev/null +++ b/Servant-API-Get.html @@ -0,0 +1,12 @@ +Servant.API.Get

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Get

Synopsis

Documentation

data Get a Source

Endpoint for simple GET requests. Serves the result as JSON.

Example:

type MyApi = "books" :> Get [Book]

Instances

VLinkHelper * (Get x) 
ToJSON result => HasServer (Get result)

When implementing the handler for a Get endpoint, + just like for Delete, Post + and Put, the handler code runs in the + EitherT (Int, String) IO monad, where the Int represents + the status code and the String a message, returned in case of + failure. You can quite handily use left + to quickly fail if some conditions are not met.

If successfully returning a value, we just require that its type has + a ToJSON instance and servant takes care of encoding it for you, + yielding status code 200 along the way.

Typeable (* -> *) Get 
type Server (Get result) = EitherT (Int, String) IO result 
\ No newline at end of file diff --git a/Servant-API-Post.html b/Servant-API-Post.html new file mode 100644 index 00000000..3fe7bf2d --- /dev/null +++ b/Servant-API-Post.html @@ -0,0 +1,17 @@ +Servant.API.Post

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Post

Synopsis

Documentation

data Post a Source

Endpoint for POST requests. The type variable represents the type of the + response body (not the request body, use RQBody for + that).

Example:

           -- POST /books
+           -- with a JSON encoded Book as the request body
+           -- returning the just-created Book
+type MyApi = "books" :> ReqBody Book :> Post Book

Instances

VLinkHelper * (Post x) 
ToJSON a => HasServer (Post a)

When implementing the handler for a Post endpoint, + just like for Delete, Get + and Put, the handler code runs in the + EitherT (Int, String) IO monad, where the Int represents + the status code and the String a message, returned in case of + failure. You can quite handily use left + to quickly fail if some conditions are not met.

If successfully returning a value, we just require that its type has + a ToJSON instance and servant takes care of encoding it for you, + yielding status code 201 along the way.

Typeable (* -> *) Post 
type Server (Post a) = EitherT (Int, String) IO a 
\ No newline at end of file diff --git a/Servant-API-Put.html b/Servant-API-Put.html new file mode 100644 index 00000000..745cfe56 --- /dev/null +++ b/Servant-API-Put.html @@ -0,0 +1,15 @@ +Servant.API.Put

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Put

Synopsis

Documentation

data Put a Source

Endpoint for PUT requests, usually used to update a ressource. + The type a is the type of the response body that's returned.

Example:

-- PUT /books/:isbn
+-- with a Book as request body, returning the updated Book
+type MyApi = "books" :> Capture "isbn" Text :> ReqBody Book :> Put Book

Instances

ToJSON a => HasServer (Put a)

When implementing the handler for a Put endpoint, + just like for Delete, Get + and Post, the handler code runs in the + EitherT (Int, String) IO monad, where the Int represents + the status code and the String a message, returned in case of + failure. You can quite handily use left + to quickly fail if some conditions are not met.

If successfully returning a value, we just require that its type has + a ToJSON instance and servant takes care of encoding it for you, + yielding status code 200 along the way.

Typeable (* -> *) Put 
type Server (Put a) = EitherT (Int, String) IO a 
\ No newline at end of file diff --git a/Servant-API-QueryParam.html b/Servant-API-QueryParam.html new file mode 100644 index 00000000..ff1ea9d7 --- /dev/null +++ b/Servant-API-QueryParam.html @@ -0,0 +1,45 @@ +Servant.API.QueryParam

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.QueryParam

Synopsis

Documentation

data QueryParam sym a Source

Lookup the value associated to the sym query string parameter + and try to extract it as a value of type a.

Example:

-- /books?author=<author name>
+type MyApi = "books" :> QueryParam "author" Text :> Get [Book]

Instances

(KnownSymbol sym, FromText a, HasServer sublayout) => HasServer ((:>) * (QueryParam Symbol * sym a) sublayout)

If you use QueryParam "author" Text in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type Maybe Text.

This lets servant worry about looking it up in the query string + and turning it into a value of the type you specify, enclosed + in Maybe, because it may not be there and servant would then + hand you Nothing.

You can control how it'll be converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
+
+server :: Server MyApi
+server = getBooksBy
+  where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book]
+        getBooksBy Nothing       = ...return all books...
+        getBooksBy (Just author) = ...return books by the given author...
type Server ((:>) * (QueryParam Symbol * sym a) sublayout) = Maybe a -> Server sublayout 

data QueryParams sym a Source

Lookup the values associated to the sym query string parameter + and try to extract it as a value of type [a]. This is typically + meant to support query string parameters of the form + param[]=val1&param[]=val2 and so on. Note that servant doesn't actually + require the []s and will fetch the values just fine with + param=val1&param=val2, too.

Example:

-- /books?authors[]=<author1>&authors[]=<author2>&...
+type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]

Instances

(KnownSymbol sym, FromText a, HasServer sublayout) => HasServer ((:>) * (QueryParams Symbol * sym a) sublayout)

If you use QueryParams "authors" Text in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type [Text].

This lets servant worry about looking up 0 or more values in the query string + associated to authors and turning each of them into a value of + the type you specify.

You can control how the individual values are converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
+
+server :: Server MyApi
+server = getBooksBy
+  where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book]
+        getBooksBy authors = ...return all books by these authors...
type Server ((:>) * (QueryParams Symbol * sym a) sublayout) = [a] -> Server sublayout 

data QueryFlag sym Source

Lookup a potentially value-less query string parameter + with boolean semantics. If the param sym is there without any value, + or if it's there with value "true" or "1", it's interpreted as True. + Otherwise, it's interpreted as False.

Example:

-- /books?published
+type MyApi = "books" :> QueryFlag "published" :> Get [Book]

Instances

(KnownSymbol sym, HasServer sublayout) => HasServer ((:>) * (QueryFlag Symbol sym) sublayout)

If you use QueryFlag "published" in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type Bool.

Example:

type MyApi = "books" :> QueryFlag "published" :> Get [Book]
+
+server :: Server MyApi
+server = getBooks
+  where getBooks :: Bool -> EitherT (Int, String) IO [Book]
+        getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
type Server ((:>) * (QueryFlag Symbol sym) sublayout) = Bool -> Server sublayout 
\ No newline at end of file diff --git a/Servant-API-Raw.html b/Servant-API-Raw.html new file mode 100644 index 00000000..21834152 --- /dev/null +++ b/Servant-API-Raw.html @@ -0,0 +1,11 @@ +Servant.API.Raw

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Raw

Synopsis

Documentation

data Raw Source

Endpoint for plugging in your own Wai Applications.

The given Application will get the request as received by the server, potentially with + a modified (stripped) pathInfo if the Application is being routed with :>.

In addition to just letting you plug in your existing WAI Applications, + this can also be used with serveDirectory to serve + static files stored in a particular directory on your filesystem, or to serve + your API's documentation with serveDocumentation.

Instances

HasServer Raw

Just pass the request to the underlying application and serve its response.

Example:

type MyApi = "images" :> Raw
+
+server :: Server MyApi
+server = serveDirectory "/var/www/images"
type Server Raw = Application 
\ No newline at end of file diff --git a/Servant-API-ReqBody.html b/Servant-API-ReqBody.html new file mode 100644 index 00000000..fa32de1a --- /dev/null +++ b/Servant-API-ReqBody.html @@ -0,0 +1,14 @@ +Servant.API.ReqBody

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.ReqBody

Synopsis

Documentation

data ReqBody a Source

Extract the request body as a value of type a.

Example:

           -- POST /books
+type MyApi = "books" :> ReqBody Book :> Post Book

Instances

(FromJSON a, HasServer sublayout) => HasServer ((:>) * (ReqBody * a) sublayout)

If you use ReqBody in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of the type specified by ReqBody. + This lets servant worry about extracting it from the request and turning + it into a value of the type you specify.

All it asks is for a FromJSON instance.

Example:

type MyApi = "books" :> ReqBody Book :> Post Book
+
+server :: Server MyApi
+server = postBook
+  where postBook :: Book -> EitherT (Int, String) IO Book
+        postBook book = ...insert into your db...
type Server ((:>) * (ReqBody * a) sublayout) = a -> Server sublayout 
\ No newline at end of file diff --git a/Servant-API-Sub.html b/Servant-API-Sub.html new file mode 100644 index 00000000..6376dd4a --- /dev/null +++ b/Servant-API-Sub.html @@ -0,0 +1,56 @@ +Servant.API.Sub

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API.Sub

Synopsis

Documentation

data path :> a infixr 9 Source

The contained API (second argument) can be found under ("/" ++ path) + (path being the first argument).

Example:

-- GET /hello/world
+-- returning a JSON encoded World value
+type MyApi = "hello" :> "world" :> Get World

Constructors

(Proxy path) :> a infixr 9 

Instances

(KnownSymbol s, VLinkHelper * e) => VLinkHelper * ((:>) Symbol s e) 
(KnownSymbol capture, FromText a, HasServer sublayout) => HasServer ((:>) * (Capture Symbol * capture a) sublayout)

If you use Capture in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of the type specified by the Capture. + This lets servant worry about getting it from the URL and turning + it into a value of the type you specify.

You can control how it'll be converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> Capture "isbn" Text :> Get Book
+
+server :: Server MyApi
+server = getBook
+  where getBook :: Text -> EitherT (Int, String) IO Book
+        getBook isbn = ...
(KnownSymbol sym, HasServer sublayout) => HasServer ((:>) * (QueryFlag Symbol sym) sublayout)

If you use QueryFlag "published" in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type Bool.

Example:

type MyApi = "books" :> QueryFlag "published" :> Get [Book]
+
+server :: Server MyApi
+server = getBooks
+  where getBooks :: Bool -> EitherT (Int, String) IO [Book]
+        getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
(KnownSymbol sym, FromText a, HasServer sublayout) => HasServer ((:>) * (QueryParams Symbol * sym a) sublayout)

If you use QueryParams "authors" Text in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type [Text].

This lets servant worry about looking up 0 or more values in the query string + associated to authors and turning each of them into a value of + the type you specify.

You can control how the individual values are converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
+
+server :: Server MyApi
+server = getBooksBy
+  where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book]
+        getBooksBy authors = ...return all books by these authors...
(KnownSymbol sym, FromText a, HasServer sublayout) => HasServer ((:>) * (QueryParam Symbol * sym a) sublayout)

If you use QueryParam "author" Text in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type Maybe Text.

This lets servant worry about looking it up in the query string + and turning it into a value of the type you specify, enclosed + in Maybe, because it may not be there and servant would then + hand you Nothing.

You can control how it'll be converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
+
+server :: Server MyApi
+server = getBooksBy
+  where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book]
+        getBooksBy Nothing       = ...return all books...
+        getBooksBy (Just author) = ...return books by the given author...
(FromJSON a, HasServer sublayout) => HasServer ((:>) * (ReqBody * a) sublayout)

If you use ReqBody in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of the type specified by ReqBody. + This lets servant worry about extracting it from the request and turning + it into a value of the type you specify.

All it asks is for a FromJSON instance.

Example:

type MyApi = "books" :> ReqBody Book :> Post Book
+
+server :: Server MyApi
+server = postBook
+  where postBook :: Book -> EitherT (Int, String) IO Book
+        postBook book = ...insert into your db...
(KnownSymbol path, HasServer sublayout) => HasServer ((:>) Symbol path sublayout)

Make sure the incoming request starts with "/path", strip it and + pass the rest of the request path to sublayout.

type Server ((:>) * (Capture Symbol * capture a) sublayout) = a -> Server sublayout 
type Server ((:>) * (QueryFlag Symbol sym) sublayout) = Bool -> Server sublayout 
type Server ((:>) * (QueryParams Symbol * sym a) sublayout) = [a] -> Server sublayout 
type Server ((:>) * (QueryParam Symbol * sym a) sublayout) = Maybe a -> Server sublayout 
type Server ((:>) * (ReqBody * a) sublayout) = a -> Server sublayout 
type Server ((:>) Symbol path sublayout) = Server sublayout 
\ No newline at end of file diff --git a/Servant-API.html b/Servant-API.html new file mode 100644 index 00000000..de67263d --- /dev/null +++ b/Servant-API.html @@ -0,0 +1,4 @@ +Servant.API

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.API

Combinators

Type-level combinator for expressing subrouting: :>

Type-level combinator for alternative endpoints: :<|>

Accessing information from the request

Capturing parts of the url path as parsed values: Capture

Retrieving parameters from the query string of the URI: QueryParam

Accessing the request body as a JSON-encoded type: ReqBody

Actual endpoints, distinguished by HTTP method

GET requests

POST requests

DELETE requests

PUT requests

Untyped endpoints

Plugging in a wai Application, serving directories

Utilities

QuasiQuotes for endpoints

module Servant.QQ

Type-safe internal URLs

\ No newline at end of file diff --git a/Servant-Common-Text.html b/Servant-Common-Text.html new file mode 100644 index 00000000..8ce17b95 --- /dev/null +++ b/Servant-Common-Text.html @@ -0,0 +1,7 @@ +Servant.Common.Text

servant-0.2

Safe HaskellSafe-Inferred
LanguageHaskell2010

Servant.Common.Text

Synopsis

Documentation

class FromText a where Source

For getting values from url captures and query string parameters

Methods

fromText :: Text -> Maybe a Source

class ToText a where Source

For putting values in paths and query string parameters

Methods

toText :: a -> Text Source

\ No newline at end of file diff --git a/Servant-QQ.html b/Servant-QQ.html new file mode 100644 index 00000000..0f12ce8c --- /dev/null +++ b/Servant-QQ.html @@ -0,0 +1,17 @@ +Servant.QQ

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.QQ

Description

QuasiQuoting utilities for API types.

sitemap allows you to write your type in a very natural way:

[sitemap|
+PUT        hello                 String -> ()
+POST       hello/p:Int           String -> ()
+GET        hello/?name:String    Int
+|]
+

Will generate:

       "hello" :> ReqBody String :> Put ()
+  :<|> "hello" :> Capture "p" Int :> ReqBody String :> Post ()
+  :<|> "hello" :> QueryParam "name" String :> Get Int
+

Note the / before a QueryParam!

Synopsis

Documentation

class ExpSYM repr' repr | repr -> repr', repr' -> repr where Source

Finally-tagless encoding for our DSL. + Keeping repr' and repr distinct when writing functions with an + ExpSYM context ensures certain invariants (for instance, that there is + only one of get, post, put, and delete in a value), but + sometimes requires a little more work.

Methods

lit :: String -> repr' -> repr Source

capture :: String -> String -> repr -> repr Source

reqBody :: String -> repr -> repr Source

queryParam :: String -> String -> repr -> repr Source

conj :: repr' -> repr -> repr Source

get :: String -> repr Source

post :: String -> repr Source

put :: String -> repr Source

delete :: String -> repr Source

Instances

(>:) :: Type -> Type -> Type infixr 6 Source

parseMethod :: ExpSYM repr' repr => Parser (String -> repr) Source

parseUrlSegment :: ExpSYM repr repr => Parser (repr -> repr) Source

parseUrl :: ExpSYM repr repr => Parser (repr -> repr) Source

parseEntry :: ExpSYM repr repr => Parser repr Source

sitemap :: QuasiQuoter Source

The sitemap QuasiQuoter.

Comments are allowed, and have the standard Haskell format

  • -- for inline
  • {- ... -} for block
\ No newline at end of file diff --git a/Servant-Server.html b/Servant-Server.html new file mode 100644 index 00000000..c52cc4ae --- /dev/null +++ b/Servant-Server.html @@ -0,0 +1,116 @@ +Servant.Server

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.Server

Description

This module lets you implement Servers for defined APIs. You'll + most likely just need serve.

Implementing Servers

serve :: HasServer layout => Proxy layout -> Server layout -> Application Source

serve allows you to implement an API and produce a wai Application.

Example:

type MyApi = "books" :> Get [Book] -- GET /books
+        :<|> "books" :> ReqBody Book :> Post Book -- POST /books
+
+server :: Server MyApi
+server = listAllBooks :<|> postBook
+  where listAllBooks = ...
+        postBook book = ...
+
+app :: Application
+app = serve myApi server
+
+main :: IO ()
+main = Network.Wai.Handler.Warp.run 8080 app

Route mismatch

data RouteMismatch Source

Constructors

NotFound

the usual "not found" error

WrongMethod

a more informative "you just got the HTTP method wrong" error

InvalidBody

an even more informative "your json request body wasn't valid" error

Instances

Eq RouteMismatch 
Show RouteMismatch 
Monoid RouteMismatch
> mempty = NotFound
+>
+> NotFound    mappend           x = x
+> WrongMethod mappend InvalidBody = InvalidBody
+> WrongMethod mappend           _ = WrongMethod
+> InvalidBody mappend           _ = InvalidBody
+

newtype RouteResult a Source

A wrapper around Either RouteMismatch a.

Constructors

RR 

Instances

Eq a => Eq (RouteResult a) 
Show a => Show (RouteResult a) 
Monoid (RouteResult a)

If we get a Right, it has precedence over everything else.

This in particular means that if we could get several Rights, + only the first we encounter would be taken into account.

type RoutingApplication Source

Arguments

 = Request

the request, the field pathInfo may be modified by url routing

-> (RouteResult Response -> IO ResponseReceived) 
-> IO ResponseReceived 

class HasServer layout where Source

Associated Types

type Server layout :: * Source

Methods

route :: Proxy layout -> Server layout -> RoutingApplication Source

Instances

HasServer Delete

If you have a Delete endpoint in your API, + the handler for this endpoint is meant to delete + a resource.

The code of the handler will, just like + for Get, Post and + Put, run in EitherT (Int, String) IO (). + The Int represents the status code and the String a message + to be returned. You can use left to + painlessly error out if the conditions for a successful deletion + are not met.

HasServer Raw

Just pass the request to the underlying application and serve its response.

Example:

type MyApi = "images" :> Raw
+
+server :: Server MyApi
+server = serveDirectory "/var/www/images"
ToJSON result => HasServer (Get result)

When implementing the handler for a Get endpoint, + just like for Delete, Post + and Put, the handler code runs in the + EitherT (Int, String) IO monad, where the Int represents + the status code and the String a message, returned in case of + failure. You can quite handily use left + to quickly fail if some conditions are not met.

If successfully returning a value, we just require that its type has + a ToJSON instance and servant takes care of encoding it for you, + yielding status code 200 along the way.

ToJSON a => HasServer (Post a)

When implementing the handler for a Post endpoint, + just like for Delete, Get + and Put, the handler code runs in the + EitherT (Int, String) IO monad, where the Int represents + the status code and the String a message, returned in case of + failure. You can quite handily use left + to quickly fail if some conditions are not met.

If successfully returning a value, we just require that its type has + a ToJSON instance and servant takes care of encoding it for you, + yielding status code 201 along the way.

ToJSON a => HasServer (Put a)

When implementing the handler for a Put endpoint, + just like for Delete, Get + and Post, the handler code runs in the + EitherT (Int, String) IO monad, where the Int represents + the status code and the String a message, returned in case of + failure. You can quite handily use left + to quickly fail if some conditions are not met.

If successfully returning a value, we just require that its type has + a ToJSON instance and servant takes care of encoding it for you, + yielding status code 200 along the way.

(HasServer a, HasServer b) => HasServer ((:<|>) a b)

A server for a :<|> b first tries to match the request again the route + represented by a and if it fails tries b. You must provide a request + handler for each route.

type MyApi = "books" :> Get [Book] -- GET /books
+        :<|> "books" :> ReqBody Book :> Post Book -- POST /books
+
+server :: Server MyApi
+server = listAllBooks :<|> postBook
+  where listAllBooks = ...
+        postBook book = ...
(KnownSymbol capture, FromText a, HasServer sublayout) => HasServer ((:>) * (Capture Symbol * capture a) sublayout)

If you use Capture in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of the type specified by the Capture. + This lets servant worry about getting it from the URL and turning + it into a value of the type you specify.

You can control how it'll be converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> Capture "isbn" Text :> Get Book
+
+server :: Server MyApi
+server = getBook
+  where getBook :: Text -> EitherT (Int, String) IO Book
+        getBook isbn = ...
(KnownSymbol sym, HasServer sublayout) => HasServer ((:>) * (QueryFlag Symbol sym) sublayout)

If you use QueryFlag "published" in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type Bool.

Example:

type MyApi = "books" :> QueryFlag "published" :> Get [Book]
+
+server :: Server MyApi
+server = getBooks
+  where getBooks :: Bool -> EitherT (Int, String) IO [Book]
+        getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
(KnownSymbol sym, FromText a, HasServer sublayout) => HasServer ((:>) * (QueryParams Symbol * sym a) sublayout)

If you use QueryParams "authors" Text in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type [Text].

This lets servant worry about looking up 0 or more values in the query string + associated to authors and turning each of them into a value of + the type you specify.

You can control how the individual values are converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
+
+server :: Server MyApi
+server = getBooksBy
+  where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book]
+        getBooksBy authors = ...return all books by these authors...
(KnownSymbol sym, FromText a, HasServer sublayout) => HasServer ((:>) * (QueryParam Symbol * sym a) sublayout)

If you use QueryParam "author" Text in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of type Maybe Text.

This lets servant worry about looking it up in the query string + and turning it into a value of the type you specify, enclosed + in Maybe, because it may not be there and servant would then + hand you Nothing.

You can control how it'll be converted from Text to your type + by simply providing an instance of FromText for your type.

Example:

type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
+
+server :: Server MyApi
+server = getBooksBy
+  where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book]
+        getBooksBy Nothing       = ...return all books...
+        getBooksBy (Just author) = ...return books by the given author...
(FromJSON a, HasServer sublayout) => HasServer ((:>) * (ReqBody * a) sublayout)

If you use ReqBody in one of the endpoints for your API, + this automatically requires your server-side handler to be a function + that takes an argument of the type specified by ReqBody. + This lets servant worry about extracting it from the request and turning + it into a value of the type you specify.

All it asks is for a FromJSON instance.

Example:

type MyApi = "books" :> ReqBody Book :> Post Book
+
+server :: Server MyApi
+server = postBook
+  where postBook :: Book -> EitherT (Int, String) IO Book
+        postBook book = ...insert into your db...
(KnownSymbol path, HasServer sublayout) => HasServer ((:>) Symbol path sublayout)

Make sure the incoming request starts with "/path", strip it and + pass the rest of the request path to sublayout.

\ No newline at end of file diff --git a/Servant-Utils-Links.html b/Servant-Utils-Links.html new file mode 100644 index 00000000..1a0ddc64 --- /dev/null +++ b/Servant-Utils-Links.html @@ -0,0 +1,23 @@ +Servant.Utils.Links

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.Utils.Links

Description

Type safe internal links.

Provides the function mkLink:

  type API = Proxy ("hello" :> Get Int
+               :| "bye" :> QueryParam "name" String :> Post Bool)
+
+  api :: API
+  api = proxy
+
+  link1 :: Proxy ("hello" :> Get Int)
+  link1 = proxy
+
+  link2 :: Proxy ("hello" :> Delete)
+  link2 = proxy
+
+  mkLink link1 API  --  typechecks, returns 'Link "/hello"'
+
+  mkLink link2  API  -- doesn't typecheck
+

That is, mkLink takes two arguments, a link proxy and a sitemap, and + returns a Link, but only typechecks if the link proxy is a valid link, + and part of the sitemap.

N.B.: mkLink assumes a capture matches any string (without slashes).

Synopsis

Documentation

type family Or a b Source

Equations

Or False False = False 
Or True b = True 
Or a True = True 

type family And a b Source

Equations

And True True = True 
And a False = False 
And False b = False 

type family IsElem a s Source

Equations

IsElem e (sa :<|> sb) = Or (IsElem e sa) (IsElem e sb) 
IsElem (e :> sa) (e :> sb) = IsElem sa sb 
IsElem (e :> sa) (Capture x y :> sb) = IsElem sa sb 
IsElem sa (ReqBody x :> sb) = IsElem sa sb 
IsElem sa (QueryParam x y :> sb) = IsElem sa sb 
IsElem e e = True 
IsElem e a = False 

type family IsLink'' l Source

Equations

IsLink'' (e :> Get x) = IsLink' e 
IsLink'' (e :> Post x) = IsLink' e 
IsLink'' (e :> Put x) = IsLink' e 
IsLink'' (e :> Delete) = IsLink' e 
IsLink'' a = False 

type family IsLink' e Source

Equations

IsLink' (f :: Symbol) = True 

type family IsLink e Source

Equations

IsLink (a :> b) = Or (And (IsLink' a) (IsLink'' b)) (IsLink'' (a :> b)) 

class ValidLinkIn f s where Source

The 'ValidLinkIn f s' constraint holds when s is an API that + contains f, and f is a link.

Methods

mkLink Source

Arguments

:: f 
-> s 
-> Link

This function will only typecheck if f + is an URI within s

Instances

((~) Bool (IsElem f s) True, (~) Bool (IsLink f) True, VLinkHelper * f) => ValidLinkIn f s 

data Link Source

Constructors

Link String 

class VLinkHelper f where Source

Methods

vlh :: forall proxy. proxy f -> String Source

Instances

\ No newline at end of file diff --git a/Servant-Utils-StaticFiles.html b/Servant-Utils-StaticFiles.html new file mode 100644 index 00000000..8198ec69 --- /dev/null +++ b/Servant-Utils-StaticFiles.html @@ -0,0 +1,15 @@ +Servant.Utils.StaticFiles

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant.Utils.StaticFiles

Description

This module defines a sever-side handler that lets you serve static files.

  • serveDirectory lets you serve anything that lives under a particular + directory on your filesystem.

Documentation

serveDirectory :: FilePath -> Server Raw Source

Serve anything under the specified directory as a Raw endpoint.

type MyApi = "static" :> Raw
+
+server :: Server MyApi
+server = serveDirectory "/var/www"
+

would capture any request to /static/<something> and look for + <something> under /var/www.

It will do its best to guess the MIME type for that file, based on the extension, + and send an appropriate Content-Type header if possible.

If your goal is to serve HTML, CSS and Javascript files that use the rest of the API + as a webapp backend, you will most likely not want the static files to be hidden + behind a /static/ prefix. In that case, remember to put the serveDirectory + handler in the last position, because servant will try to match the handlers + in order.

\ No newline at end of file diff --git a/Servant.html b/Servant.html new file mode 100644 index 00000000..6a794a2c --- /dev/null +++ b/Servant.html @@ -0,0 +1,6 @@ +Servant

servant-0.2

Safe HaskellNone
LanguageHaskell2010

Servant

Documentation

This module and its submodules can be used to define servant APIs. Note + that these API definitions don't directly implement a server (or anything + else).

For implementing servers for servant APIs.

Using your types in request paths and query string parameters

Utilities on top of the servant core

module Servant.QQ

Useful re-exports

data Proxy t :: k -> *

A concrete, poly-kinded proxy type

Constructors

Proxy 

Instances

Monad (Proxy *) 
Functor (Proxy *) 
Applicative (Proxy *) 
Bounded (Proxy k s) 
Enum (Proxy k s) 
Eq (Proxy k s) 
Ord (Proxy k s) 
Read (Proxy k s) 
Show (Proxy k s) 
Ix (Proxy k s) 
Generic (Proxy * t) 
Monoid (Proxy * s) 
Typeable (k -> *) (Proxy k) 
type Rep (Proxy k t) = D1 D1Proxy (C1 C1_0Proxy U1) 
\ No newline at end of file diff --git a/doc-index.html b/doc-index.html new file mode 100644 index 00000000..1187dac7 --- /dev/null +++ b/doc-index.html @@ -0,0 +1,4 @@ +servant-0.2 (Index)

servant-0.2

Index

:<|> 
1 (Type/Class)Servant.API.Alternative, Servant.API, Servant
2 (Data Constructor)Servant.API.Alternative, Servant.API, Servant
:> 
1 (Type/Class)Servant.API.Sub, Servant.API, Servant
2 (Data Constructor)Servant.API.Sub, Servant.API, Servant
>:Servant.QQ, Servant
AndServant.Utils.Links, Servant
blockCommentServant.QQ, Servant
CaptureServant.API.Capture, Servant.API, Servant
captureServant.QQ, Servant
conjServant.QQ, Servant
DeleteServant.API.Delete, Servant.API, Servant
deleteServant.QQ, Servant
eolServant.QQ, Servant
eolsServant.QQ, Servant
ExpSYMServant.QQ, Servant
failWithServant.Server, Servant
FromTextServant.Common.Text, Servant
fromTextServant.Common.Text, Servant
GetServant.API.Get, Servant.API, Servant
getServant.QQ, Servant
HasServerServant.Server, Servant
inlineCommentServant.QQ, Servant
InvalidBodyServant.Server, Servant
IsElemServant.Utils.Links, Servant
IsLinkServant.Utils.Links, Servant
IsLink'Servant.Utils.Links, Servant
IsLink''Servant.Utils.Links, Servant
isMismatchServant.Server, Servant
Link 
1 (Type/Class)Servant.Utils.Links, Servant
2 (Data Constructor)Servant.Utils.Links, Servant
litServant.QQ, Servant
mkLinkServant.Utils.Links, Servant.API, Servant
NotFoundServant.Server, Servant
OrServant.Utils.Links, Servant
parseAllServant.QQ, Servant
parseEntryServant.QQ, Servant
parseMethodServant.QQ, Servant
parseTypServant.QQ, Servant
parseUrlServant.QQ, Servant
parseUrlSegmentServant.QQ, Servant
PostServant.API.Post, Servant.API, Servant
postServant.QQ, Servant
Proxy 
1 (Data Constructor)Servant
2 (Type/Class)Servant
PutServant.API.Put, Servant.API, Servant
putServant.QQ, Servant
QueryFlagServant.API.QueryParam, Servant.API, Servant
QueryParamServant.API.QueryParam, Servant.API, Servant
queryParamServant.QQ, Servant
QueryParamsServant.API.QueryParam, Servant.API, Servant
RawServant.API.Raw, Servant.API, Servant
ReqArgValServant.QQ, Servant
ReqBodyServant.API.ReqBody, Servant.API, Servant
reqBodyServant.QQ, Servant
routeServant.Server, Servant
RouteMismatchServant.Server, Servant
RouteResultServant.Server, Servant
routeResultServant.Server, Servant
RoutingApplicationServant.Server, Servant
RRServant.Server, Servant
serveServant.Server, Servant
serveDirectoryServant.Utils.StaticFiles, Servant.API, Servant
ServerServant.Server, Servant
sitemapServant.QQ, Servant.API, Servant
succeedWithServant.Server, Servant
toApplicationServant.Server, Servant
ToTextServant.Common.Text, Servant
toTextServant.Common.Text, Servant
TypServant.QQ, Servant
ValServant.QQ, Servant
ValidLinkInServant.Utils.Links, Servant
vlhServant.Utils.Links, Servant
VLinkHelperServant.Utils.Links, Servant
WrongMethodServant.Server, Servant
\ No newline at end of file diff --git a/frames.html b/frames.html new file mode 100644 index 00000000..1b4e38d4 --- /dev/null +++ b/frames.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/haddock-util.js b/haddock-util.js new file mode 100644 index 00000000..9a6fccf7 --- /dev/null +++ b/haddock-util.js @@ -0,0 +1,344 @@ +// Haddock JavaScript utilities + +var rspace = /\s\s+/g, + rtrim = /^\s+|\s+$/g; + +function spaced(s) { return (" " + s + " ").replace(rspace, " "); } +function trim(s) { return s.replace(rtrim, ""); } + +function hasClass(elem, value) { + var className = spaced(elem.className || ""); + return className.indexOf( " " + value + " " ) >= 0; +} + +function addClass(elem, value) { + var className = spaced(elem.className || ""); + if ( className.indexOf( " " + value + " " ) < 0 ) { + elem.className = trim(className + " " + value); + } +} + +function removeClass(elem, value) { + var className = spaced(elem.className || ""); + className = className.replace(" " + value + " ", " "); + elem.className = trim(className); +} + +function toggleClass(elem, valueOn, valueOff, bool) { + if (bool == null) { bool = ! hasClass(elem, valueOn); } + if (bool) { + removeClass(elem, valueOff); + addClass(elem, valueOn); + } + else { + removeClass(elem, valueOn); + addClass(elem, valueOff); + } + return bool; +} + + +function makeClassToggle(valueOn, valueOff) +{ + return function(elem, bool) { + return toggleClass(elem, valueOn, valueOff, bool); + } +} + +toggleShow = makeClassToggle("show", "hide"); +toggleCollapser = makeClassToggle("collapser", "expander"); + +function toggleSection(id) +{ + var b = toggleShow(document.getElementById("section." + id)); + toggleCollapser(document.getElementById("control." + id), b); + rememberCollapsed(id, b); + return b; +} + +var collapsed = {}; +function rememberCollapsed(id, b) +{ + if(b) + delete collapsed[id] + else + collapsed[id] = null; + + var sections = []; + for(var i in collapsed) + { + if(collapsed.hasOwnProperty(i)) + sections.push(i); + } + // cookie specific to this page; don't use setCookie which sets path=/ + document.cookie = "collapsed=" + escape(sections.join('+')); +} + +function restoreCollapsed() +{ + var cookie = getCookie("collapsed"); + if(!cookie) + return; + + var ids = cookie.split('+'); + for(var i in ids) + { + if(document.getElementById("section." + ids[i])) + toggleSection(ids[i]); + } +} + +function setCookie(name, value) { + document.cookie = name + "=" + escape(value) + ";path=/;"; +} + +function clearCookie(name) { + document.cookie = name + "=;path=/;expires=Thu, 01-Jan-1970 00:00:01 GMT;"; +} + +function getCookie(name) { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) { + return unescape(c.substring(nameEQ.length,c.length)); + } + } + return null; +} + + + +var max_results = 75; // 50 is not enough to search for map in the base libraries +var shown_range = null; +var last_search = null; + +function quick_search() +{ + perform_search(false); +} + +function full_search() +{ + perform_search(true); +} + + +function perform_search(full) +{ + var text = document.getElementById("searchbox").value.toLowerCase(); + if (text == last_search && !full) return; + last_search = text; + + var table = document.getElementById("indexlist"); + var status = document.getElementById("searchmsg"); + var children = table.firstChild.childNodes; + + // first figure out the first node with the prefix + var first = bisect(-1); + var last = (first == -1 ? -1 : bisect(1)); + + if (first == -1) + { + table.className = ""; + status.innerHTML = "No results found, displaying all"; + } + else if (first == 0 && last == children.length - 1) + { + table.className = ""; + status.innerHTML = ""; + } + else if (last - first >= max_results && !full) + { + table.className = ""; + status.innerHTML = "More than " + max_results + ", press Search to display"; + } + else + { + // decide what you need to clear/show + if (shown_range) + setclass(shown_range[0], shown_range[1], "indexrow"); + setclass(first, last, "indexshow"); + shown_range = [first, last]; + table.className = "indexsearch"; + status.innerHTML = ""; + } + + + function setclass(first, last, status) + { + for (var i = first; i <= last; i++) + { + children[i].className = status; + } + } + + + // do a binary search, treating 0 as ... + // return either -1 (no 0's found) or location of most far match + function bisect(dir) + { + var first = 0, finish = children.length - 1; + var mid, success = false; + + while (finish - first > 3) + { + mid = Math.floor((finish + first) / 2); + + var i = checkitem(mid); + if (i == 0) i = dir; + if (i == -1) + finish = mid; + else + first = mid; + } + var a = (dir == 1 ? first : finish); + var b = (dir == 1 ? finish : first); + for (var i = b; i != a - dir; i -= dir) + { + if (checkitem(i) == 0) return i; + } + return -1; + } + + + // from an index, decide what the result is + // 0 = match, -1 is lower, 1 is higher + function checkitem(i) + { + var s = getitem(i).toLowerCase().substr(0, text.length); + if (s == text) return 0; + else return (s > text ? -1 : 1); + } + + + // from an index, get its string + // this abstracts over alternates + function getitem(i) + { + for ( ; i >= 0; i--) + { + var s = children[i].firstChild.firstChild.data; + if (s.indexOf(' ') == -1) + return s; + } + return ""; // should never be reached + } +} + +function setSynopsis(filename) { + if (parent.window.synopsis) { + if (parent.window.synopsis.location.replace) { + // In Firefox this avoids adding the change to the history. + parent.window.synopsis.location.replace(filename); + } else { + parent.window.synopsis.location = filename; + } + } +} + +function addMenuItem(html) { + var menu = document.getElementById("page-menu"); + if (menu) { + var btn = menu.firstChild.cloneNode(false); + btn.innerHTML = html; + menu.appendChild(btn); + } +} + +function adjustForFrames() { + var bodyCls; + + if (parent.location.href == window.location.href) { + // not in frames, so add Frames button + addMenuItem("Frames"); + bodyCls = "no-frame"; + } + else { + bodyCls = "in-frame"; + } + addClass(document.body, bodyCls); +} + +function reframe() { + setCookie("haddock-reframe", document.URL); + window.location = "frames.html"; +} + +function postReframe() { + var s = getCookie("haddock-reframe"); + if (s) { + parent.window.main.location = s; + clearCookie("haddock-reframe"); + } +} + +function styles() { + var i, a, es = document.getElementsByTagName("link"), rs = []; + for (i = 0; a = es[i]; i++) { + if(a.rel.indexOf("style") != -1 && a.title) { + rs.push(a); + } + } + return rs; +} + +function addStyleMenu() { + var as = styles(); + var i, a, btns = ""; + for(i=0; a = as[i]; i++) { + btns += "
  • " + + a.title + "
  • " + } + if (as.length > 1) { + var h = "
    " + + "Style ▾" + + "" + + "
    "; + addMenuItem(h); + } +} + +function setActiveStyleSheet(title) { + var as = styles(); + var i, a, found; + for(i=0; a = as[i]; i++) { + a.disabled = true; + // need to do this always, some browsers are edge triggered + if(a.title == title) { + found = a; + } + } + if (found) { + found.disabled = false; + setCookie("haddock-style", title); + } + else { + as[0].disabled = false; + clearCookie("haddock-style"); + } + styleMenu(false); +} + +function resetStyle() { + var s = getCookie("haddock-style"); + if (s) setActiveStyleSheet(s); +} + + +function styleMenu(show) { + var m = document.getElementById('style-menu'); + if (m) toggleShow(m, show); +} + + +function pageLoad() { + addStyleMenu(); + adjustForFrames(); + resetStyle(); + restoreCollapsed(); +} + diff --git a/hslogo-16.png b/hslogo-16.png new file mode 100644 index 0000000000000000000000000000000000000000..0ff8579fbd897417b0d6dad6e920f8882138a7c0 GIT binary patch literal 1684 zcmV;F25b3=P)4Tx0C)j~RL^S@K@|QrZmG~B2wH0nvUrdpNm;9CMbtL^5n^i$+aIn^?(HA4aZWV5ov6ELTdbo0FI&wK{O>*+w4vx20?>!`FrQsdJlnHR>OPy zcd~b_n$otK2Za4V;76L-DzNVtaSB-y0*E}{p()372;bw_^6ZZ}PI-92wGS&j#91PI zKs7DSe@(bk%_Y-7gGe}(^>I=@oY#w#*Bu9GZf3^F5WP>3rn}7Ut74&?PWBFvy`A)a zPP5)V!Xd&78LdA?xQ(9mjMYElVd13a#D+Z_7&Y|xU=_C-srWU*6kiZcC!$nw*)9$7 zn6CX+@=AhmkT}X@VSsa5NKe;HZuq)~1$`#h6R+ZTR#D-3j}vF!)ZOnz+5)dI4jl{{ z44Mr{P!L4~VVJN`K!!XTF*LGrKO?IK8z<8w`3e3jI8lUGNUta*C8 zn(P`s>{pjD=7Kek#B;Fw@hxAK%$F&Q6vg9J^Xf~4by_hu-=A!MJ3Znq&n~srbFGPs zH&&aMXZ>nO`|hf|ljc?VPhR!${AbO?W8x_>CU%PFA&Hm8F7cAsOREdwU~R_;ot1_u z(ruCYB-LPGn!NQdT|ZlRy+(fw^-+`=%+gee_kY4FWHg<*4sZI8+sFJD270UUORdLHO0nA4V) z%{fwsET5CQ>B?eK%uw4yQc~9?*JVo2}ze(;aRcp*ceL#HUJSllrgm5wQKR zQu+C;QrUh^8rFfA`ftFz{YAidi-`aL010qNS#tmY4c7nw4c7reD4Tcy00T@(L_t(I z5sj2vNEA^R$7gqDc6T=2^@fUA2(c`MltuL5<|KW>RWz$&YbU@|M|{$E*8Tu-Ux!w z1Y*Dr&Ubfr&v-nZaaB{3ilRumrjPmk{sZvQEWlW+{o~IH|8)=s6c#X9S5s5d%J z4@)&QH5|xQY-)^L1n0pTRu0Lx9`08YTjTwn^6 z0;b1+aQ@)n;Em$q;=7BBi)v0zj&o^g>0Whp^_^5IbxIUP8C@y9;R?*Ouu}rmfxbU= zwtWVNke-m!=`7bYEhWpcI5#)9qp`8E0lr6IQ)ARL3Ui}Af@grj8aN1=r>Cb+prlzO zNfJs*N_tUm2ZL%5* zPmL2??da$TR904gL(VDAQ-Fv_Dk}Pdw*4T(%*f4MKLRg=4ekMjhe2mW zMFsBwg%ftWT}0kxRaIk1k7qJ8*#cKB;Ft{i`zVIs-Nqge;!!Ld7#O&Qqu7e0sJmP) z$MW*>L$vSB&dxp@iA3U9fo)-7!Czlr{|o7Hv{1oyg3xsu%gn@(b1>$;SM-ZaQ`HV=V0s;lr%d8bd;xY zGwNvm3=Iu=tyXIgtJnf@A(2S@M140N ew{UA~tMxaJq;$xaSSi*30000servant-0.2 \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..d18ea030 --- /dev/null +++ b/index.html @@ -0,0 +1,4 @@ +servant-0.2

    servant-0.2

    \ No newline at end of file diff --git a/mini_Servant-API-Alternative.html b/mini_Servant-API-Alternative.html new file mode 100644 index 00000000..5e2e0937 --- /dev/null +++ b/mini_Servant-API-Alternative.html @@ -0,0 +1,4 @@ +Servant.API.Alternative

    Servant.API.Alternative

    data a :<|> b

    \ No newline at end of file diff --git a/mini_Servant-API-Capture.html b/mini_Servant-API-Capture.html new file mode 100644 index 00000000..f2ad8622 --- /dev/null +++ b/mini_Servant-API-Capture.html @@ -0,0 +1,4 @@ +Servant.API.Capture

    Servant.API.Capture

    data Capture sym a

    \ No newline at end of file diff --git a/mini_Servant-API-Delete.html b/mini_Servant-API-Delete.html new file mode 100644 index 00000000..5b11363c --- /dev/null +++ b/mini_Servant-API-Delete.html @@ -0,0 +1,4 @@ +Servant.API.Delete

    Servant.API.Delete

    data Delete

    \ No newline at end of file diff --git a/mini_Servant-API-Get.html b/mini_Servant-API-Get.html new file mode 100644 index 00000000..03e9ebf4 --- /dev/null +++ b/mini_Servant-API-Get.html @@ -0,0 +1,4 @@ +Servant.API.Get

    Servant.API.Get

    data Get a

    \ No newline at end of file diff --git a/mini_Servant-API-Post.html b/mini_Servant-API-Post.html new file mode 100644 index 00000000..7ed013a8 --- /dev/null +++ b/mini_Servant-API-Post.html @@ -0,0 +1,4 @@ +Servant.API.Post

    Servant.API.Post

    data Post a

    \ No newline at end of file diff --git a/mini_Servant-API-Put.html b/mini_Servant-API-Put.html new file mode 100644 index 00000000..0a39d3b1 --- /dev/null +++ b/mini_Servant-API-Put.html @@ -0,0 +1,4 @@ +Servant.API.Put

    Servant.API.Put

    data Put a

    \ No newline at end of file diff --git a/mini_Servant-API-QueryParam.html b/mini_Servant-API-QueryParam.html new file mode 100644 index 00000000..5016b335 --- /dev/null +++ b/mini_Servant-API-QueryParam.html @@ -0,0 +1,4 @@ +Servant.API.QueryParam

    Servant.API.QueryParam

    data QueryParam sym a

    data QueryParams sym a

    data QueryFlag sym

    \ No newline at end of file diff --git a/mini_Servant-API-Raw.html b/mini_Servant-API-Raw.html new file mode 100644 index 00000000..5450dc62 --- /dev/null +++ b/mini_Servant-API-Raw.html @@ -0,0 +1,4 @@ +Servant.API.Raw

    Servant.API.Raw

    data Raw

    \ No newline at end of file diff --git a/mini_Servant-API-ReqBody.html b/mini_Servant-API-ReqBody.html new file mode 100644 index 00000000..20ba5bb2 --- /dev/null +++ b/mini_Servant-API-ReqBody.html @@ -0,0 +1,4 @@ +Servant.API.ReqBody

    Servant.API.ReqBody

    data ReqBody a

    \ No newline at end of file diff --git a/mini_Servant-API-Sub.html b/mini_Servant-API-Sub.html new file mode 100644 index 00000000..079f2e46 --- /dev/null +++ b/mini_Servant-API-Sub.html @@ -0,0 +1,4 @@ +Servant.API.Sub

    Servant.API.Sub

    data path :> a

    \ No newline at end of file diff --git a/mini_Servant-API.html b/mini_Servant-API.html new file mode 100644 index 00000000..0e8aa4d6 --- /dev/null +++ b/mini_Servant-API.html @@ -0,0 +1,4 @@ +Servant.API

    Servant.API

    Combinators

    Accessing information from the request

    Actual endpoints, distinguished by HTTP method

    Untyped endpoints

    Utilities

    \ No newline at end of file diff --git a/mini_Servant-Common-Text.html b/mini_Servant-Common-Text.html new file mode 100644 index 00000000..5961e8df --- /dev/null +++ b/mini_Servant-Common-Text.html @@ -0,0 +1,4 @@ +Servant.Common.Text

    Servant.Common.Text

    class FromText a

    class ToText a

    \ No newline at end of file diff --git a/mini_Servant-QQ.html b/mini_Servant-QQ.html new file mode 100644 index 00000000..874d956d --- /dev/null +++ b/mini_Servant-QQ.html @@ -0,0 +1,4 @@ +Servant.QQ

    Servant.QQ

    \ No newline at end of file diff --git a/mini_Servant-Server.html b/mini_Servant-Server.html new file mode 100644 index 00000000..752cd3c9 --- /dev/null +++ b/mini_Servant-Server.html @@ -0,0 +1,4 @@ +Servant.Server

    Servant.Server

    \ No newline at end of file diff --git a/mini_Servant-Utils-Links.html b/mini_Servant-Utils-Links.html new file mode 100644 index 00000000..b8816028 --- /dev/null +++ b/mini_Servant-Utils-Links.html @@ -0,0 +1,4 @@ +Servant.Utils.Links

    Servant.Utils.Links

    type family Or a b

    type family And a b

    type family IsElem a s

    type family IsLink'' l

    type family IsLink' e

    type family IsLink e

    class ValidLinkIn f s

    data Link

    class VLinkHelper f

    \ No newline at end of file diff --git a/mini_Servant-Utils-StaticFiles.html b/mini_Servant-Utils-StaticFiles.html new file mode 100644 index 00000000..c8ebe5f6 --- /dev/null +++ b/mini_Servant-Utils-StaticFiles.html @@ -0,0 +1,4 @@ +Servant.Utils.StaticFiles

    Servant.Utils.StaticFiles

    \ No newline at end of file diff --git a/mini_Servant.html b/mini_Servant.html new file mode 100644 index 00000000..9e47337d --- /dev/null +++ b/mini_Servant.html @@ -0,0 +1,4 @@ +Servant

    Servant

    data Proxy t

    \ No newline at end of file diff --git a/minus.gif b/minus.gif new file mode 100644 index 0000000000000000000000000000000000000000..1deac2fe1a42e35b994f1b855488f392c50f6a89 GIT binary patch literal 56 zcmZ?wbhEHb * { + font-size: 93%; /* 12pt */ +} + +#mini #module-list .caption, +#mini #module-header .caption { + font-size: 125%; /* 15pt */ +} + +#mini #interface h1, +#mini #interface h2, +#mini #interface h3, +#mini #interface h4 { + font-size: 109%; /* 13pt */ + margin: 1em 0 0; +} + +#mini #interface .top, +#mini #interface .src { + margin: 0; +} + +#mini #module-list ul { + list-style: none; + margin: 0; +} + +#alphabet ul { + list-style: none; + padding: 0; + margin: 0.5em 0 0; + text-align: center; +} + +#alphabet li { + display: inline; + margin: 0 0.25em; +} + +#alphabet a { + font-weight: bold; +} + +#index .caption, +#module-list .caption { font-size: 131%; /* 17pt */ } + +#index table { + margin-left: 2em; +} + +#index .src { + font-weight: bold; +} +#index .alt { + font-size: 77%; /* 10pt */ + font-style: italic; + padding-left: 2em; +} + +#index td + td { + padding-left: 1em; +} + +#module-list ul { + list-style: none; + margin: 0 0 0 2em; +} + +#module-list li { + clear: right; +} + +#module-list span.collapser, +#module-list span.expander { + background-position: 0 0.3em; +} + +#module-list .package { + float: right; +} + +/* @end */ diff --git a/plus.gif b/plus.gif new file mode 100644 index 0000000000000000000000000000000000000000..2d15c14173d23f664b955cd24f51c82f5f09d91d GIT binary patch literal 59 zcmZ?wbhEHbgbBX M^XE!9f*2UA0nx1yDgXcg literal 0 HcmV?d00001 diff --git a/servant.haddock b/servant.haddock new file mode 100644 index 0000000000000000000000000000000000000000..c055746964c3654835618841182d79a978682968 GIT binary patch literal 56217 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 T2z&IoiD!EsPaYorsQdo_6r|#h literal 0 HcmV?d00001 diff --git a/servant.txt b/servant.txt new file mode 100644 index 00000000..d34cc636 --- /dev/null +++ b/servant.txt @@ -0,0 +1,611 @@ +-- Hoogle documentation, generated by Haddock +-- See Hoogle, http://www.haskell.org/hoogle/ + + +@package servant +@version 0.2 + +module Servant.Common.Text + +-- | For getting values from url captures and query string parameters +class FromText a +fromText :: FromText a => Text -> Maybe a + +-- | For putting values in paths and query string parameters +class ToText a +toText :: ToText a => a -> Text +instance ToText Float +instance FromText Float +instance ToText Double +instance FromText Double +instance ToText Integer +instance FromText Integer +instance ToText Word64 +instance FromText Word64 +instance ToText Word32 +instance FromText Word32 +instance ToText Word16 +instance FromText Word16 +instance ToText Word8 +instance FromText Word8 +instance ToText Word +instance FromText Word +instance ToText Int64 +instance FromText Int64 +instance ToText Int32 +instance FromText Int32 +instance ToText Int16 +instance FromText Int16 +instance ToText Int8 +instance FromText Int8 +instance ToText Int +instance FromText Int +instance ToText Bool +instance FromText Bool +instance ToText String +instance FromText String +instance ToText Text +instance FromText Text + + +-- | This module lets you implement Servers for defined APIs. You'll +-- most likely just need serve. +module Servant.Server + +-- | serve allows you to implement an API and produce a wai +-- Application. +-- +-- Example: +-- +--
    +--   type MyApi = "books" :> Get [Book] -- GET /books
    +--           :<|> "books" :> ReqBody Book :> Post Book -- POST /books
    +--   
    +--   server :: Server MyApi
    +--   server = listAllBooks :<|> postBook
    +--     where listAllBooks = ...
    +--           postBook book = ...
    +--   
    +--   app :: Application
    +--   app = serve myApi server
    +--   
    +--   main :: IO ()
    +--   main = Network.Wai.Handler.Warp.run 8080 app
    +--   
    +serve :: HasServer layout => Proxy layout -> Server layout -> Application +toApplication :: RoutingApplication -> Application +data RouteMismatch + +-- | the usual "not found" error +NotFound :: RouteMismatch + +-- | a more informative "you just got the HTTP method wrong" error +WrongMethod :: RouteMismatch + +-- | an even more informative "your json request body wasn't valid" error +InvalidBody :: RouteMismatch + +-- |
    +--   > mempty = NotFound
    +--   >
    +--   > NotFound    mappend           x = x
    +--   > WrongMethod mappend InvalidBody = InvalidBody
    +--   > WrongMethod mappend           _ = WrongMethod
    +--   > InvalidBody mappend           _ = InvalidBody
    +--   
    + +-- | A wrapper around Either RouteMismatch a. +newtype RouteResult a +RR :: Either RouteMismatch a -> RouteResult a +routeResult :: RouteResult a -> Either RouteMismatch a +failWith :: RouteMismatch -> RouteResult a +succeedWith :: a -> RouteResult a +isMismatch :: RouteResult a -> Bool + +-- | If we get a Right, it has precedence over everything else. +-- +-- This in particular means that if we could get several Rights, +-- only the first we encounter would be taken into account. +type RoutingApplication = Request -> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived +class HasServer layout where type family Server layout :: * +route :: HasServer layout => Proxy layout -> Server layout -> RoutingApplication +instance Eq RouteMismatch +instance Show RouteMismatch +instance Eq a => Eq (RouteResult a) +instance Show a => Show (RouteResult a) +instance Monoid (RouteResult a) +instance Monoid RouteMismatch + +module Servant.API.Sub + +-- | The contained API (second argument) can be found under ("/" ++ +-- path) (path being the first argument). +-- +-- Example: +-- +--
    +--   -- GET /hello/world
    +--   -- returning a JSON encoded World value
    +--   type MyApi = "hello" :> "world" :> Get World
    +--   
    +data (:>) (path :: k) a +(:>) :: Proxy path -> a -> (:>) a + +-- | Make sure the incoming request starts with "/path", strip it +-- and pass the rest of the request path to sublayout. +instance (KnownSymbol path, HasServer sublayout) => HasServer (path :> sublayout) + +module Servant.API.Alternative + +-- | Union of two APIs, first takes precedence in case of overlap. +-- +-- Example: +-- +--
    +--   type MyApi = "books" :> Get [Book] -- GET /books
    +--           :<|> "books" :> ReqBody Book :> Post Book -- POST /books
    +--   
    +data (:<|>) a b +(:<|>) :: a -> b -> (:<|>) a b + +-- | A server for a :<|> b first tries to match the +-- request again the route represented by a and if it fails +-- tries b. You must provide a request handler for each route. +-- +--
    +--   type MyApi = "books" :> Get [Book] -- GET /books
    +--           :<|> "books" :> ReqBody Book :> Post Book -- POST /books
    +--   
    +--   server :: Server MyApi
    +--   server = listAllBooks :<|> postBook
    +--     where listAllBooks = ...
    +--           postBook book = ...
    +--   
    +instance (HasServer a, HasServer b) => HasServer (a :<|> b) + +module Servant.API.Capture + +-- | Capture a value from the request path under a certain type a. +-- +-- Example: +-- +--
    +--              -- GET /books/:isbn
    +--   type MyApi = "books" :> Capture "isbn" Text :> Get Book
    +--   
    +data Capture sym a +instance (KnownSymbol capture, FromText a, HasServer sublayout) => HasServer (Capture capture a :> sublayout) + +module Servant.API.QueryParam + +-- | Lookup the value associated to the sym query string parameter +-- and try to extract it as a value of type a. +-- +-- Example: +-- +--
    +--   -- /books?author=<author name>
    +--   type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
    +--   
    +data QueryParam sym a + +-- | If you use QueryParam "author" Text in one of the +-- endpoints for your API, this automatically requires your server-side +-- handler to be a function that takes an argument of type +-- Maybe Text. +-- +-- This lets servant worry about looking it up in the query string and +-- turning it into a value of the type you specify, enclosed in +-- Maybe, because it may not be there and servant would then hand +-- you Nothing. +-- +-- You can control how it'll be converted from Text to your type +-- by simply providing an instance of FromText for your type. +-- +-- Example: +-- +--
    +--   type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
    +--   
    +--   server :: Server MyApi
    +--   server = getBooksBy
    +--     where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book]
    +--           getBooksBy Nothing       = ...return all books...
    +--           getBooksBy (Just author) = ...return books by the given author...
    +--   
    + +-- | Lookup the values associated to the sym query string +-- parameter and try to extract it as a value of type [a]. This +-- is typically meant to support query string parameters of the form +-- param[]=val1&param[]=val2 and so on. Note that servant +-- doesn't actually require the []s and will fetch the values +-- just fine with param=val1&param=val2, too. +-- +-- Example: +-- +--
    +--   -- /books?authors[]=<author1>&authors[]=<author2>&...
    +--   type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
    +--   
    +data QueryParams sym a + +-- | If you use QueryParams "authors" Text in one of the +-- endpoints for your API, this automatically requires your server-side +-- handler to be a function that takes an argument of type +-- [Text]. +-- +-- This lets servant worry about looking up 0 or more values in the query +-- string associated to authors and turning each of them into a +-- value of the type you specify. +-- +-- You can control how the individual values are converted from +-- Text to your type by simply providing an instance of +-- FromText for your type. +-- +-- Example: +-- +--
    +--   type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
    +--   
    +--   server :: Server MyApi
    +--   server = getBooksBy
    +--     where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book]
    +--           getBooksBy authors = ...return all books by these authors...
    +--   
    + +-- | Lookup a potentially value-less query string parameter with boolean +-- semantics. If the param sym is there without any value, or if +-- it's there with value "true" or "1", it's interpreted as True. +-- Otherwise, it's interpreted as False. +-- +-- Example: +-- +--
    +--   -- /books?published
    +--   type MyApi = "books" :> QueryFlag "published" :> Get [Book]
    +--   
    +data QueryFlag sym + +-- | If you use QueryFlag "published" in one of the +-- endpoints for your API, this automatically requires your server-side +-- handler to be a function that takes an argument of type Bool. +-- +-- Example: +-- +--
    +--   type MyApi = "books" :> QueryFlag "published" :> Get [Book]
    +--   
    +--   server :: Server MyApi
    +--   server = getBooks
    +--     where getBooks :: Bool -> EitherT (Int, String) IO [Book]
    +--           getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
    +--   
    +instance (KnownSymbol sym, HasServer sublayout) => HasServer (QueryFlag sym :> sublayout) +instance (KnownSymbol sym, FromText a, HasServer sublayout) => HasServer (QueryParams sym a :> sublayout) +instance (KnownSymbol sym, FromText a, HasServer sublayout) => HasServer (QueryParam sym a :> sublayout) + +module Servant.API.ReqBody + +-- | Extract the request body as a value of type a. +-- +-- Example: +-- +--
    +--              -- POST /books
    +--   type MyApi = "books" :> ReqBody Book :> Post Book
    +--   
    +data ReqBody a + +-- | If you use ReqBody in one of the endpoints for your API, this +-- automatically requires your server-side handler to be a function that +-- takes an argument of the type specified by ReqBody. This lets +-- servant worry about extracting it from the request and turning it into +-- a value of the type you specify. +-- +-- All it asks is for a FromJSON instance. +-- +-- Example: +-- +--
    +--   type MyApi = "books" :> ReqBody Book :> Post Book
    +--   
    +--   server :: Server MyApi
    +--   server = postBook
    +--     where postBook :: Book -> EitherT (Int, String) IO Book
    +--           postBook book = ...insert into your db...
    +--   
    +instance (FromJSON a, HasServer sublayout) => HasServer (ReqBody a :> sublayout) + +module Servant.API.Get + +-- | Endpoint for simple GET requests. Serves the result as JSON. +-- +-- Example: +-- +--
    +--   type MyApi = "books" :> Get [Book]
    +--   
    +data Get a + +-- | When implementing the handler for a Get endpoint, just like for +-- Delete, Post and Put, the handler code runs in +-- the EitherT (Int, String) IO monad, where the Int +-- represents the status code and the String a message, returned +-- in case of failure. You can quite handily use left to quickly +-- fail if some conditions are not met. +-- +-- If successfully returning a value, we just require that its type has a +-- ToJSON instance and servant takes care of encoding it for you, +-- yielding status code 200 along the way. +instance Typeable Get +instance ToJSON result => HasServer (Get result) + +module Servant.API.Post + +-- | Endpoint for POST requests. The type variable represents the type of +-- the response body (not the request body, use RQBody for that). +-- +-- Example: +-- +--
    +--              -- POST /books
    +--              -- with a JSON encoded Book as the request body
    +--              -- returning the just-created Book
    +--   type MyApi = "books" :> ReqBody Book :> Post Book
    +--   
    +data Post a + +-- | When implementing the handler for a Post endpoint, just like +-- for Delete, Get and Put, the handler code runs in +-- the EitherT (Int, String) IO monad, where the Int +-- represents the status code and the String a message, returned +-- in case of failure. You can quite handily use left to quickly +-- fail if some conditions are not met. +-- +-- If successfully returning a value, we just require that its type has a +-- ToJSON instance and servant takes care of encoding it for you, +-- yielding status code 201 along the way. +instance Typeable Post +instance ToJSON a => HasServer (Post a) + +module Servant.API.Delete + +-- | Combinator for DELETE requests. +-- +-- Example: +-- +--
    +--              -- DELETE /books/:isbn
    +--   type MyApi = "books" :> Capture "isbn" Text :> Delete
    +--   
    +data Delete + +-- | If you have a Delete endpoint in your API, the handler for this +-- endpoint is meant to delete a resource. +-- +-- The code of the handler will, just like for Get, Post +-- and Put, run in EitherT (Int, String) IO (). The +-- Int represents the status code and the String a message +-- to be returned. You can use left to painlessly error out if the +-- conditions for a successful deletion are not met. +instance Typeable Delete +instance HasServer Delete + +module Servant.API.Put + +-- | Endpoint for PUT requests, usually used to update a ressource. The +-- type a is the type of the response body that's returned. +-- +-- Example: +-- +--
    +--   -- PUT /books/:isbn
    +--   -- with a Book as request body, returning the updated Book
    +--   type MyApi = "books" :> Capture "isbn" Text :> ReqBody Book :> Put Book
    +--   
    +data Put a + +-- | When implementing the handler for a Put endpoint, just like for +-- Delete, Get and Post, the handler code runs in +-- the EitherT (Int, String) IO monad, where the Int +-- represents the status code and the String a message, returned +-- in case of failure. You can quite handily use left to quickly +-- fail if some conditions are not met. +-- +-- If successfully returning a value, we just require that its type has a +-- ToJSON instance and servant takes care of encoding it for you, +-- yielding status code 200 along the way. +instance Typeable Put +instance ToJSON a => HasServer (Put a) + + +-- | QuasiQuoting utilities for API types. +-- +-- sitemap allows you to write your type in a very natural way: +-- +--
    +--   [sitemap|
    +--   PUT        hello                 String -> ()
    +--   POST       hello/p:Int           String -> ()
    +--   GET        hello/?name:String    Int
    +--   |]
    +--   
    +-- +-- Will generate: +-- +--
    +--        "hello" :> ReqBody String :> Put ()
    +--   :<|> "hello" :> Capture "p" Int :> ReqBody String :> Post ()
    +--   :<|> "hello" :> QueryParam "name" String :> Get Int
    +--   
    +-- +-- Note the / before a QueryParam! +module Servant.QQ + +-- | Finally-tagless encoding for our DSL. Keeping repr' and +-- repr distinct when writing functions with an ExpSYM +-- context ensures certain invariants (for instance, that there is only +-- one of get, post, put, and delete in a +-- value), but sometimes requires a little more work. +class ExpSYM repr' repr | repr -> repr', repr' -> repr +lit :: ExpSYM repr' repr => String -> repr' -> repr +capture :: ExpSYM repr' repr => String -> String -> repr -> repr +reqBody :: ExpSYM repr' repr => String -> repr -> repr +queryParam :: ExpSYM repr' repr => String -> String -> repr -> repr +conj :: ExpSYM repr' repr => repr' -> repr -> repr +get :: ExpSYM repr' repr => String -> repr +post :: ExpSYM repr' repr => String -> repr +put :: ExpSYM repr' repr => String -> repr +delete :: ExpSYM repr' repr => String -> repr +(>:) :: Type -> Type -> Type +parseMethod :: ExpSYM repr' repr => Parser (String -> repr) +parseUrlSegment :: ExpSYM repr repr => Parser (repr -> repr) +parseUrl :: ExpSYM repr repr => Parser (repr -> repr) +data Typ +Val :: String -> Typ +ReqArgVal :: String -> String -> Typ +parseTyp :: Parser Typ +parseEntry :: ExpSYM repr repr => Parser repr +blockComment :: Parser () +inlineComment :: Parser () +eol :: Parser String +eols :: Parser () +parseAll :: Parser Type + +-- | The sitemap QuasiQuoter. +-- +--
      +--
    • ...var:type... becomes a +-- capture
    • +--
    • .../?var:type becomes a query +-- parameter
    • +--
    • method ... typ becomes a method returning +-- typ
    • +--
    • method ... typ1 -> typ2 becomes a +-- method with request body of typ1 and returning +-- typ2
    • +--
    +-- +-- Comments are allowed, and have the standard Haskell format +-- +--
      +--
    • -- for inline
    • +--
    • {- ... -} for block
    • +--
    +sitemap :: QuasiQuoter +instance ExpSYM Type Type + + +-- | Type safe internal links. +-- +-- Provides the function mkLink: +-- +--
    +--   type API = Proxy ("hello" :> Get Int
    +--                :| "bye" :> QueryParam "name" String :> Post Bool)
    +--   
    +--   api :: API
    +--   api = proxy
    +--   
    +--   link1 :: Proxy ("hello" :> Get Int)
    +--   link1 = proxy
    +--   
    +--   link2 :: Proxy ("hello" :> Delete)
    +--   link2 = proxy
    +--   
    +--   mkLink link1 API  --  typechecks, returns 'Link "/hello"'
    +--   
    +--   mkLink link2  API  -- doesn't typecheck
    +--   
    +-- +-- That is, mkLink takes two arguments, a link proxy and a +-- sitemap, and returns a Link, but only typechecks if the link +-- proxy is a valid link, and part of the sitemap. +-- +-- N.B.: mkLink assumes a capture matches any string +-- (without slashes). +module Servant.Utils.Links + +-- | The 'ValidLinkIn f s' constraint holds when s is an API that +-- contains f, and f is a link. +class ValidLinkIn f s +mkLink :: ValidLinkIn f s => f -> s -> Link +data Link +Link :: String -> Link +class VLinkHelper f +vlh :: VLinkHelper f => proxy f -> String +instance Show Link +instance VLinkHelper (Post x) +instance VLinkHelper (Get x) +instance (KnownSymbol s, VLinkHelper e) => VLinkHelper (s :> e) +instance (IsElem f s ~ 'True, IsLink f ~ 'True, VLinkHelper f) => ValidLinkIn f s + +module Servant.API.Raw + +-- | Endpoint for plugging in your own Wai Applications. +-- +-- The given Application will get the request as received by the +-- server, potentially with a modified (stripped) pathInfo if the +-- Application is being routed with :>. +-- +-- In addition to just letting you plug in your existing WAI +-- Applications, this can also be used with serveDirectory +-- to serve static files stored in a particular directory on your +-- filesystem, or to serve your API's documentation with +-- serveDocumentation. +data Raw + +-- | Just pass the request to the underlying application and serve its +-- response. +-- +-- Example: +-- +--
    +--   type MyApi = "images" :> Raw
    +--   
    +--   server :: Server MyApi
    +--   server = serveDirectory "/var/www/images"
    +--   
    +instance HasServer Raw + + +-- | This module defines a sever-side handler that lets you serve static +-- files. +-- +--
      +--
    • serveDirectory lets you serve anything that lives under a +-- particular directory on your filesystem.
    • +--
    +module Servant.Utils.StaticFiles + +-- | Serve anything under the specified directory as a Raw endpoint. +-- +--
    +--   type MyApi = "static" :> Raw
    +--   
    +--   server :: Server MyApi
    +--   server = serveDirectory "/var/www"
    +--   
    +-- +-- would capture any request to /static/<something> and +-- look for <something> under /var/www. +-- +-- It will do its best to guess the MIME type for that file, based on the +-- extension, and send an appropriate Content-Type header if +-- possible. +-- +-- If your goal is to serve HTML, CSS and Javascript files that use the +-- rest of the API as a webapp backend, you will most likely not want the +-- static files to be hidden behind a /static/ prefix. In that +-- case, remember to put the serveDirectory handler in the last +-- position, because servant will try to match the handlers in +-- order. +serveDirectory :: FilePath -> Server Raw + +module Servant.API + +module Servant + +-- | A concrete, poly-kinded proxy type +data Proxy (t :: k) :: k -> * +Proxy :: Proxy diff --git a/src/Servant-API-Alternative.html b/src/Servant-API-Alternative.html new file mode 100644 index 00000000..5a001213 --- /dev/null +++ b/src/Servant-API-Alternative.html @@ -0,0 +1,50 @@ + + + + + +src/Servant/API/Alternative.hs + + + +
    {-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE TypeOperators #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +module Servant.API.Alternative where
    +
    +import Data.Monoid
    +import Data.Proxy
    +import Servant.Server
    +
    +-- | Union of two APIs, first takes precedence in case of overlap.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> Get [Book] -- GET /books
    +-- >         :<|> "books" :> ReqBody Book :> Post Book -- POST /books
    +data a :<|> b = a :<|> b
    +infixr 8 :<|>
    +
    +-- | A server for @a ':<|>' b@ first tries to match the request again the route
    +--   represented by @a@ and if it fails tries @b@. You must provide a request
    +--   handler for each route.
    +--
    +-- > type MyApi = "books" :> Get [Book] -- GET /books
    +-- >         :<|> "books" :> ReqBody Book :> Post Book -- POST /books
    +-- >
    +-- > server :: Server MyApi
    +-- > server = listAllBooks :<|> postBook
    +-- >   where listAllBooks = ...
    +-- >         postBook book = ...
    +instance (HasServer a, HasServer b) => HasServer (a :<|> b) where
    +  type Server (a :<|> b) = Server a :<|> Server b
    +  route Proxy (a :<|> b) request respond =
    +    route pa a request $ \ mResponse ->
    +      if isMismatch mResponse
    +        then route pb b request $ \mResponse' -> respond (mResponse <> mResponse')
    +        else respond mResponse
    +
    +    where pa = Proxy :: Proxy a
    +          pb = Proxy :: Proxy b
    +
    + diff --git a/src/Servant-API-Capture.html b/src/Servant-API-Capture.html new file mode 100644 index 00000000..217ad660 --- /dev/null +++ b/src/Servant-API-Capture.html @@ -0,0 +1,71 @@ + + + + + +src/Servant/API/Capture.hs + + + +
    {-# LANGUAGE PolyKinds #-}
    +{-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE TypeOperators #-}
    +{-# LANGUAGE FlexibleContexts #-}
    +{-# LANGUAGE FlexibleInstances #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +module Servant.API.Capture (Capture) where
    +
    +import Data.Proxy
    +import Data.Text
    +import GHC.TypeLits
    +import Network.Wai
    +import Servant.API.Sub
    +import Servant.Common.Text
    +import Servant.Server
    +
    +-- | Capture a value from the request path under a certain type @a@.
    +--
    +-- Example:
    +--
    +-- >            -- GET /books/:isbn
    +-- > type MyApi = "books" :> Capture "isbn" Text :> Get Book
    +data Capture sym a
    +
    +captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a
    +captured _ = fromText
    +
    +-- | If you use 'Capture' in one of the endpoints for your API,
    +-- this automatically requires your server-side handler to be a function
    +-- that takes an argument of the type specified by the 'Capture'.
    +-- This lets servant worry about getting it from the URL and turning
    +-- it into a value of the type you specify.
    +--
    +-- You can control how it'll be converted from 'Text' to your type
    +-- by simply providing an instance of 'FromText' for your type.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> Capture "isbn" Text :> Get Book
    +-- >
    +-- > server :: Server MyApi
    +-- > server = getBook
    +-- >   where getBook :: Text -> EitherT (Int, String) IO Book
    +-- >         getBook isbn = ...
    +instance (KnownSymbol capture, FromText a, HasServer sublayout)
    +      => HasServer (Capture capture a :> sublayout) where
    +
    +  type Server (Capture capture a :> sublayout) =
    +     a -> Server sublayout
    +
    +  route Proxy subserver request respond = case pathInfo request of
    +    (first : rest)
    +      -> case captured captureProxy first of
    +           Nothing  -> respond $ failWith NotFound
    +           Just v   -> route (Proxy :: Proxy sublayout) (subserver v) request{
    +                         pathInfo = rest
    +                       } respond
    +    _ -> respond $ failWith NotFound
    +
    +    where captureProxy = Proxy :: Proxy (Capture capture a)
    +
    + diff --git a/src/Servant-API-Delete.html b/src/Servant-API-Delete.html new file mode 100644 index 00000000..098e282a --- /dev/null +++ b/src/Servant-API-Delete.html @@ -0,0 +1,59 @@ + + + + + +src/Servant/API/Delete.hs + + + +
    {-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +{-# LANGUAGE DeriveDataTypeable #-}
    +module Servant.API.Delete where
    +
    +import Control.Monad.Trans.Either
    +import Data.Proxy
    +import Data.String.Conversions
    +import Data.Typeable
    +import Network.HTTP.Types
    +import Network.Wai
    +import Servant.Server
    +
    +-- | Combinator for DELETE requests.
    +--
    +-- Example:
    +--
    +-- >            -- DELETE /books/:isbn
    +-- > type MyApi = "books" :> Capture "isbn" Text :> Delete
    +data Delete
    +  deriving Typeable
    +
    +-- | If you have a 'Delete' endpoint in your API,
    +-- the handler for this endpoint is meant to delete
    +-- a resource.
    +--
    +-- The code of the handler will, just like
    +-- for 'Servant.API.Get.Get', 'Servant.API.Post.Post' and
    +-- 'Servant.API.Put.Put', run in @EitherT (Int, String) IO ()@.
    +-- The 'Int' represents the status code and the 'String' a message
    +-- to be returned. You can use 'Control.Monad.Trans.Either.left' to
    +-- painlessly error out if the conditions for a successful deletion
    +-- are not met.
    +instance HasServer Delete where
    +  type Server Delete = EitherT (Int, String) IO ()
    +
    +  route Proxy action request respond
    +    | null (pathInfo request) && requestMethod request == methodDelete = do
    +        e <- runEitherT action
    +        respond $ succeedWith $ case e of
    +          Right () ->
    +            responseLBS status204 [] ""
    +          Left (status, message) ->
    +            responseLBS (mkStatus status (cs message)) [] (cs message)
    +    | null (pathInfo request) && requestMethod request /= methodDelete =
    +        respond $ failWith WrongMethod
    +    | otherwise = respond $ failWith NotFound
    +
    + diff --git a/src/Servant-API-Get.html b/src/Servant-API-Get.html new file mode 100644 index 00000000..06cb305d --- /dev/null +++ b/src/Servant-API-Get.html @@ -0,0 +1,58 @@ + + + + + +src/Servant/API/Get.hs + + + +
    {-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +{-# LANGUAGE DeriveDataTypeable #-}
    +module Servant.API.Get where
    +
    +import Control.Monad.Trans.Either
    +import Data.Aeson
    +import Data.Proxy
    +import Data.String.Conversions
    +import Data.Typeable
    +import Network.HTTP.Types
    +import Network.Wai
    +import Servant.Server
    +
    +-- | Endpoint for simple GET requests. Serves the result as JSON.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> Get [Book]
    +data Get a
    +  deriving Typeable
    +
    +-- | When implementing the handler for a 'Get' endpoint,
    +-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Post.Post'
    +-- and 'Servant.API.Put.Put', the handler code runs in the
    +-- @EitherT (Int, String) IO@ monad, where the 'Int' represents
    +-- the status code and the 'String' a message, returned in case of
    +-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
    +-- to quickly fail if some conditions are not met.
    +--
    +-- If successfully returning a value, we just require that its type has
    +-- a 'ToJSON' instance and servant takes care of encoding it for you,
    +-- yielding status code 200 along the way.
    +instance ToJSON result => HasServer (Get result) where
    +  type Server (Get result) = EitherT (Int, String) IO result
    +  route Proxy action request respond
    +    | null (pathInfo request) && requestMethod request == methodGet = do
    +        e <- runEitherT action
    +        respond . succeedWith $ case e of
    +          Right output ->
    +            responseLBS ok200 [("Content-Type", "application/json")] (encode output)
    +          Left (status, message) ->
    +            responseLBS (mkStatus status (cs message)) [] (cs message)
    +    | null (pathInfo request) && requestMethod request /= methodGet =
    +        respond $ failWith WrongMethod
    +    | otherwise = respond $ failWith NotFound
    +
    + diff --git a/src/Servant-API-Post.html b/src/Servant-API-Post.html new file mode 100644 index 00000000..7c3fb040 --- /dev/null +++ b/src/Servant-API-Post.html @@ -0,0 +1,64 @@ + + + + + +src/Servant/API/Post.hs + + + +
    {-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +{-# LANGUAGE DeriveDataTypeable #-}
    +module Servant.API.Post where
    +
    +import Control.Monad.Trans.Either
    +import Data.Aeson
    +import Data.Proxy
    +import Data.String.Conversions
    +import Data.Typeable
    +import Network.HTTP.Types
    +import Network.Wai
    +import Servant.Server
    +
    +-- | Endpoint for POST requests. The type variable represents the type of the
    +-- response body (not the request body, use 'Servant.API.RQBody.RQBody' for
    +-- that).
    +--
    +-- Example:
    +--
    +-- >            -- POST /books
    +-- >            -- with a JSON encoded Book as the request body
    +-- >            -- returning the just-created Book
    +-- > type MyApi = "books" :> ReqBody Book :> Post Book
    +data Post a
    +  deriving Typeable
    +
    +-- | When implementing the handler for a 'Post' endpoint,
    +-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
    +-- and 'Servant.API.Put.Put', the handler code runs in the
    +-- @EitherT (Int, String) IO@ monad, where the 'Int' represents
    +-- the status code and the 'String' a message, returned in case of
    +-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
    +-- to quickly fail if some conditions are not met.
    +--
    +-- If successfully returning a value, we just require that its type has
    +-- a 'ToJSON' instance and servant takes care of encoding it for you,
    +-- yielding status code 201 along the way.
    +instance ToJSON a => HasServer (Post a) where
    +  type Server (Post a) = EitherT (Int, String) IO a
    +
    +  route Proxy action request respond
    +    | null (pathInfo request) && requestMethod request == methodPost = do
    +        e <- runEitherT action
    +        respond . succeedWith $ case e of
    +          Right out ->
    +            responseLBS status201 [("Content-Type", "application/json")] (encode out)
    +          Left (status, message) ->
    +            responseLBS (mkStatus status (cs message)) [] (cs message)
    +    | null (pathInfo request) && requestMethod request /= methodPost =
    +        respond $ failWith WrongMethod
    +    | otherwise = respond $ failWith NotFound
    +
    + diff --git a/src/Servant-API-Put.html b/src/Servant-API-Put.html new file mode 100644 index 00000000..8f2cf24e --- /dev/null +++ b/src/Servant-API-Put.html @@ -0,0 +1,63 @@ + + + + + +src/Servant/API/Put.hs + + + +
    {-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +{-# LANGUAGE DeriveDataTypeable #-}
    +module Servant.API.Put where
    +
    +import Control.Monad.Trans.Either
    +import Data.Aeson
    +import Data.Proxy
    +import Data.String.Conversions
    +import Data.Typeable
    +import Network.HTTP.Types
    +import Network.Wai
    +import Servant.Server
    +
    +-- | Endpoint for PUT requests, usually used to update a ressource.
    +-- The type @a@ is the type of the response body that's returned.
    +--
    +-- Example:
    +--
    +-- > -- PUT /books/:isbn
    +-- > -- with a Book as request body, returning the updated Book
    +-- > type MyApi = "books" :> Capture "isbn" Text :> ReqBody Book :> Put Book
    +data Put a
    +  deriving Typeable
    +
    +-- | When implementing the handler for a 'Put' endpoint,
    +-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
    +-- and 'Servant.API.Post.Post', the handler code runs in the
    +-- @EitherT (Int, String) IO@ monad, where the 'Int' represents
    +-- the status code and the 'String' a message, returned in case of
    +-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
    +-- to quickly fail if some conditions are not met.
    +--
    +-- If successfully returning a value, we just require that its type has
    +-- a 'ToJSON' instance and servant takes care of encoding it for you,
    +-- yielding status code 200 along the way.
    +instance ToJSON a => HasServer (Put a) where
    +  type Server (Put a) = EitherT (Int, String) IO a
    +
    +  route Proxy action request respond
    +    | null (pathInfo request) && requestMethod request == methodPut = do
    +        e <- runEitherT action
    +        respond . succeedWith $ case e of
    +          Right out ->
    +            responseLBS ok200 [("Content-Type", "application/json")] (encode out)
    +          Left (status, message) ->
    +            responseLBS (mkStatus status (cs message)) [] (cs message)
    +    | null (pathInfo request) && requestMethod request /= methodPut =
    +        respond $ failWith WrongMethod
    +
    +    | otherwise = respond $ failWith NotFound
    +
    + diff --git a/src/Servant-API-QueryParam.html b/src/Servant-API-QueryParam.html new file mode 100644 index 00000000..73bae268 --- /dev/null +++ b/src/Servant-API-QueryParam.html @@ -0,0 +1,173 @@ + + + + + +src/Servant/API/QueryParam.hs + + + +
    {-# LANGUAGE PolyKinds #-}
    +{-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE TypeOperators #-}
    +{-# LANGUAGE FlexibleContexts #-}
    +{-# LANGUAGE FlexibleInstances #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +module Servant.API.QueryParam where
    +
    +import Data.Maybe
    +import Data.Proxy
    +import Data.String.Conversions
    +import GHC.TypeLits
    +import Network.HTTP.Types
    +import Network.Wai
    +import Servant.API.Sub
    +import Servant.Common.Text
    +import Servant.Server
    +
    +-- | Lookup the value associated to the @sym@ query string parameter
    +-- and try to extract it as a value of type @a@.
    +--
    +-- Example:
    +--
    +-- > -- /books?author=<author name>
    +-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
    +data QueryParam sym a
    +
    +-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
    +-- this automatically requires your server-side handler to be a function
    +-- that takes an argument of type @'Maybe' 'Text'@.
    +--
    +-- This lets servant worry about looking it up in the query string
    +-- and turning it into a value of the type you specify, enclosed
    +-- in 'Maybe', because it may not be there and servant would then
    +-- hand you 'Nothing'.
    +--
    +-- You can control how it'll be converted from 'Text' to your type
    +-- by simply providing an instance of 'FromText' for your type.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
    +-- >
    +-- > server :: Server MyApi
    +-- > server = getBooksBy
    +-- >   where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book]
    +-- >         getBooksBy Nothing       = ...return all books...
    +-- >         getBooksBy (Just author) = ...return books by the given author...
    +instance (KnownSymbol sym, FromText a, HasServer sublayout)
    +      => HasServer (QueryParam sym a :> sublayout) where
    +
    +  type Server (QueryParam sym a :> sublayout) =
    +    Maybe a -> Server sublayout
    +
    +  route Proxy subserver request respond = do
    +    let querytext = parseQueryText $ rawQueryString request
    +        param =
    +          case lookup paramname querytext of
    +            Nothing       -> Nothing -- param absent from the query string
    +            Just Nothing  -> Nothing -- param present with no value -> Nothing
    +            Just (Just v) -> fromText v -- if present, we try to convert to
    +                                        -- the right type
    +
    +    route (Proxy :: Proxy sublayout) (subserver param) request respond
    +
    +    where paramname = cs $ symbolVal (Proxy :: Proxy sym)
    +
    +-- | Lookup the values associated to the @sym@ query string parameter
    +-- and try to extract it as a value of type @[a]@. This is typically
    +-- meant to support query string parameters of the form
    +-- @param[]=val1&param[]=val2@ and so on. Note that servant doesn't actually
    +-- require the @[]@s and will fetch the values just fine with
    +-- @param=val1&param=val2@, too.
    +--
    +-- Example:
    +--
    +-- > -- /books?authors[]=<author1>&authors[]=<author2>&...
    +-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
    +data QueryParams sym a
    +
    +-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
    +-- this automatically requires your server-side handler to be a function
    +-- that takes an argument of type @['Text']@.
    +--
    +-- This lets servant worry about looking up 0 or more values in the query string
    +-- associated to @authors@ and turning each of them into a value of
    +-- the type you specify.
    +--
    +-- You can control how the individual values are converted from 'Text' to your type
    +-- by simply providing an instance of 'FromText' for your type.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
    +-- >
    +-- > server :: Server MyApi
    +-- > server = getBooksBy
    +-- >   where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book]
    +-- >         getBooksBy authors = ...return all books by these authors...
    +instance (KnownSymbol sym, FromText a, HasServer sublayout)
    +      => HasServer (QueryParams sym a :> sublayout) where
    +
    +  type Server (QueryParams sym a :> sublayout) =
    +    [a] -> Server sublayout
    +
    +  route Proxy subserver request respond = do
    +    let querytext = parseQueryText $ rawQueryString request
    +        -- if sym is "foo", we look for query string parameters
    +        -- named "foo" or "foo[]" and call fromText on the
    +        -- corresponding values
    +        parameters = filter looksLikeParam querytext
    +        values = catMaybes $ map (convert . snd) parameters
    +
    +    route (Proxy :: Proxy sublayout) (subserver values) request respond
    +
    +    where paramname = cs $ symbolVal (Proxy :: Proxy sym)
    +          looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
    +          convert Nothing = Nothing
    +          convert (Just v) = fromText v
    +
    +-- | Lookup a potentially value-less query string parameter
    +-- with boolean semantics. If the param @sym@ is there without any value,
    +-- or if it's there with value "true" or "1", it's interpreted as 'True'.
    +-- Otherwise, it's interpreted as 'False'.
    +--
    +-- Example:
    +--
    +-- > -- /books?published
    +-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book]
    +data QueryFlag sym
    +
    +-- | If you use @'QueryFlag' "published"@ in one of the endpoints for your API,
    +-- this automatically requires your server-side handler to be a function
    +-- that takes an argument of type 'Bool'.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book]
    +-- >
    +-- > server :: Server MyApi
    +-- > server = getBooks
    +-- >   where getBooks :: Bool -> EitherT (Int, String) IO [Book]
    +-- >         getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
    +instance (KnownSymbol sym, HasServer sublayout)
    +      => HasServer (QueryFlag sym :> sublayout) where
    +
    +  type Server (QueryFlag sym :> sublayout) =
    +    Bool -> Server sublayout
    +
    +  route Proxy subserver request respond = do
    +    let querytext = parseQueryText $ rawQueryString request
    +        param = case lookup paramname querytext of
    +          Just Nothing  -> True  -- param is there, with no value
    +          Just (Just v) -> examine v -- param with a value
    +          Nothing       -> False -- param not in the query string
    +
    +    route (Proxy :: Proxy sublayout) (subserver param) request respond
    +
    +    where paramname = cs $ symbolVal (Proxy :: Proxy sym)
    +          examine v | v == "true" || v == "1" || v == "" = True
    +                    | otherwise = False
    +
    + diff --git a/src/Servant-API-Raw.html b/src/Servant-API-Raw.html new file mode 100644 index 00000000..7926622f --- /dev/null +++ b/src/Servant-API-Raw.html @@ -0,0 +1,43 @@ + + + + + +src/Servant/API/Raw.hs + + + +
    {-# LANGUAGE InstanceSigs #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +{-# LANGUAGE TypeFamilies #-}
    +module Servant.API.Raw where
    +
    +import Data.Proxy
    +import Network.Wai
    +import Servant.Server
    +
    +-- | Endpoint for plugging in your own Wai 'Application's.
    +--
    +-- The given 'Application' will get the request as received by the server, potentially with
    +-- a modified (stripped) 'pathInfo' if the 'Application' is being routed with 'Servant.API.Sub.:>'.
    +--
    +-- In addition to just letting you plug in your existing WAI 'Application's,
    +-- this can also be used with 'Servant.Utils.StaticFiles.serveDirectory' to serve
    +-- static files stored in a particular directory on your filesystem, or to serve
    +-- your API's documentation with 'Servant.Utils.StaticFiles.serveDocumentation'.
    +data Raw
    +
    +-- | Just pass the request to the underlying application and serve its response.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "images" :> Raw
    +-- >
    +-- > server :: Server MyApi
    +-- > server = serveDirectory "/var/www/images"
    +instance HasServer Raw where
    +  type Server Raw = Application
    +  route Proxy rawApplication request respond =
    +    rawApplication request (respond . succeedWith)
    +
    + diff --git a/src/Servant-API-ReqBody.html b/src/Servant-API-ReqBody.html new file mode 100644 index 00000000..23e06bc0 --- /dev/null +++ b/src/Servant-API-ReqBody.html @@ -0,0 +1,60 @@ + + + + + +src/Servant/API/ReqBody.hs + + + +
    {-# LANGUAGE PolyKinds #-}
    +{-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE TypeOperators #-}
    +{-# LANGUAGE FlexibleInstances #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +module Servant.API.ReqBody where
    +
    +import Control.Applicative
    +import Data.Aeson
    +import Data.Proxy
    +import Network.Wai
    +import Servant.API.Sub
    +import Servant.Server
    +
    +-- | Extract the request body as a value of type @a@.
    +--
    +-- Example:
    +--
    +-- >            -- POST /books
    +-- > type MyApi = "books" :> ReqBody Book :> Post Book
    +data ReqBody a
    +
    +-- | If you use 'ReqBody' in one of the endpoints for your API,
    +-- this automatically requires your server-side handler to be a function
    +-- that takes an argument of the type specified by 'ReqBody'.
    +-- This lets servant worry about extracting it from the request and turning
    +-- it into a value of the type you specify.
    +--
    +-- All it asks is for a 'FromJSON' instance.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> ReqBody Book :> Post Book
    +-- >
    +-- > server :: Server MyApi
    +-- > server = postBook
    +-- >   where postBook :: Book -> EitherT (Int, String) IO Book
    +-- >         postBook book = ...insert into your db...
    +instance (FromJSON a, HasServer sublayout)
    +      => HasServer (ReqBody a :> sublayout) where
    +
    +  type Server (ReqBody a :> sublayout) =
    +    a -> Server sublayout
    +
    +  route Proxy subserver request respond = do
    +    mrqbody <- decode' <$> lazyRequestBody request
    +    case mrqbody of
    +      Nothing -> respond $ failWith InvalidBody
    +      Just v  -> route (Proxy :: Proxy sublayout) (subserver v) request respond
    +
    + diff --git a/src/Servant-API-Sub.html b/src/Servant-API-Sub.html new file mode 100644 index 00000000..3bbb6942 --- /dev/null +++ b/src/Servant-API-Sub.html @@ -0,0 +1,47 @@ + + + + + +src/Servant/API/Sub.hs + + + +
    {-# LANGUAGE PolyKinds #-}
    +{-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE TypeOperators #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +module Servant.API.Sub where
    +
    +import Data.Proxy
    +import Data.String.Conversions
    +import GHC.TypeLits
    +import Network.Wai
    +import Servant.Server
    +
    +-- | The contained API (second argument) can be found under @("/" ++ path)@
    +-- (path being the first argument).
    +--
    +-- Example:
    +--
    +-- > -- GET /hello/world
    +-- > -- returning a JSON encoded World value
    +-- > type MyApi = "hello" :> "world" :> Get World
    +data (path :: k) :> a = Proxy path :> a
    +infixr 9 :>
    +
    +-- | Make sure the incoming request starts with @"/path"@, strip it and
    +-- pass the rest of the request path to @sublayout@.
    +instance (KnownSymbol path, HasServer sublayout) => HasServer (path :> sublayout) where
    +  type Server (path :> sublayout) = Server sublayout
    +  route Proxy subserver request respond = case pathInfo request of
    +    (first : rest)
    +      | first == cs (symbolVal proxyPath)
    +      -> route (Proxy :: Proxy sublayout) subserver request{
    +           pathInfo = rest
    +         } respond
    +    _ -> respond $ failWith NotFound
    +
    +    where proxyPath = Proxy :: Proxy path
    +
    + diff --git a/src/Servant-API.html b/src/Servant-API.html new file mode 100644 index 00000000..657dffc0 --- /dev/null +++ b/src/Servant-API.html @@ -0,0 +1,62 @@ + + + + + +src/Servant/API.hs + + + +
    module Servant.API (
    +
    +  -- * Combinators
    +  -- | Type-level combinator for expressing subrouting: @':>'@
    +  module Servant.API.Sub,
    +  -- | Type-level combinator for alternative endpoints: @':<|>'@
    +  module Servant.API.Alternative,
    +
    +  -- * Accessing information from the request
    +  -- | Capturing parts of the url path as parsed values: @'Capture'@
    +  module Servant.API.Capture,
    +  -- | Retrieving parameters from the query string of the 'URI': @'QueryParam'@
    +  module Servant.API.QueryParam,
    +  -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
    +  module Servant.API.ReqBody,
    +
    +  -- * Actual endpoints, distinguished by HTTP method
    +  -- | GET requests
    +  module Servant.API.Get,
    +  -- | POST requests
    +  module Servant.API.Post,
    +  -- | DELETE requests
    +  module Servant.API.Delete,
    +  -- | PUT requests
    +  module Servant.API.Put,
    +
    +  -- * Untyped endpoints
    +  -- | Plugging in a wai 'Network.Wai.Application', serving directories
    +  module Servant.API.Raw,
    +  module Servant.Utils.StaticFiles,
    +
    +  -- * Utilities
    +  -- | QuasiQuotes for endpoints
    +  module Servant.QQ,
    +  -- | Type-safe internal URLs
    +  module Servant.Utils.Links,
    +  ) where
    +
    +import Servant.API.Alternative
    +import Servant.API.Capture
    +import Servant.API.Delete
    +import Servant.API.Get
    +import Servant.API.Post
    +import Servant.API.Put
    +import Servant.API.QueryParam
    +import Servant.API.Raw
    +import Servant.API.ReqBody
    +import Servant.API.Sub
    +import Servant.QQ (sitemap)
    +import Servant.Utils.Links (mkLink)
    +import Servant.Utils.StaticFiles
    +
    + diff --git a/src/Servant-Common-Text.html b/src/Servant-Common-Text.html new file mode 100644 index 00000000..6c39e67d --- /dev/null +++ b/src/Servant-Common-Text.html @@ -0,0 +1,141 @@ + + + + + +src/Servant/Common/Text.hs + + + +
    {-# LANGUAGE FlexibleInstances #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +{-# LANGUAGE TypeSynonymInstances #-}
    +module Servant.Common.Text
    +  ( FromText(..)
    +  , ToText(..)
    +  ) where
    +
    +import Data.String.Conversions
    +import Data.Int
    +import Data.Text
    +import Data.Text.Read
    +import Data.Word
    +
    +-- | For getting values from url captures and query string parameters
    +class FromText a where
    +  fromText :: Text -> Maybe a
    +
    +-- | For putting values in paths and query string parameters
    +class ToText a where
    +  toText :: a -> Text
    +
    +instance FromText Text where
    +  fromText = Just
    +
    +instance ToText Text where
    +  toText = id
    +
    +instance FromText String where
    +  fromText = Just . cs
    +
    +instance ToText String where
    +  toText = cs
    +
    +-- |
    +-- > fromText "true"  = Just True
    +-- > fromText "false" = Just False
    +-- > fromText _       = Nothing
    +instance FromText Bool where
    +  fromText "true"  = Just True
    +  fromText "false" = Just False
    +  fromText _       = Nothing
    +
    +-- |
    +-- > toText True  = "true"
    +-- > toText False = "false"
    +instance ToText Bool where
    +  toText True  = "true"
    +  toText False = "false"
    +
    +instance FromText Int where
    +  fromText = runReader (signed decimal)
    +
    +instance ToText Int where
    +  toText = cs . show
    +
    +instance FromText Int8 where
    +  fromText = runReader (signed decimal)
    +
    +instance ToText Int8 where
    +  toText = cs . show
    +
    +instance FromText Int16 where
    +  fromText = runReader (signed decimal)
    +
    +instance ToText Int16 where
    +  toText = cs . show
    +
    +instance FromText Int32 where
    +  fromText = runReader (signed decimal)
    +
    +instance ToText Int32 where
    +  toText = cs . show
    +
    +instance FromText Int64 where
    +  fromText = runReader (signed decimal)
    +
    +instance ToText Int64 where
    +  toText = cs . show
    +
    +instance FromText Word where
    +  fromText = runReader decimal
    +
    +instance ToText Word where
    +  toText = cs . show
    +
    +instance FromText Word8 where
    +  fromText = runReader decimal
    +
    +instance ToText Word8 where
    +  toText = cs . show
    +
    +instance FromText Word16 where
    +  fromText = runReader decimal
    +
    +instance ToText Word16 where
    +  toText = cs . show
    +
    +instance FromText Word32 where
    +  fromText = runReader decimal
    +
    +instance ToText Word32 where
    +  toText = cs . show
    +
    +instance FromText Word64 where
    +  fromText = runReader decimal
    +
    +instance ToText Word64 where
    +  toText = cs . show
    +
    +instance FromText Integer where
    +  fromText = runReader decimal
    +
    +instance ToText Integer where
    +  toText = cs . show
    +
    +instance FromText Double where
    +  fromText = runReader rational
    +
    +instance ToText Double where
    +  toText = cs . show
    +
    +instance FromText Float where
    +  fromText = runReader rational
    +
    +instance ToText Float where
    +  toText = cs . show
    +
    +runReader :: Reader a -> Text -> Maybe a
    +runReader reader t = either (const Nothing) (Just . fst) $ reader t
    +
    + diff --git a/src/Servant-QQ.html b/src/Servant-QQ.html new file mode 100644 index 00000000..7b7f8d3c --- /dev/null +++ b/src/Servant-QQ.html @@ -0,0 +1,209 @@ + + + + + +src/Servant/QQ.hs + + + +
    {-# LANGUAGE MultiParamTypeClasses #-}
    +{-# LANGUAGE FunctionalDependencies #-}
    +{-# LANGUAGE DataKinds #-}
    +{-# LANGUAGE FlexibleInstances #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +{-# LANGUAGE TemplateHaskell #-}
    +{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
    +-- | QuasiQuoting utilities for API types.
    +--
    +-- 'sitemap' allows you to write your type in a very natural way:
    +--
    +-- @
    +-- [sitemap|
    +-- PUT        hello                 String -> ()
    +-- POST       hello/p:Int           String -> ()
    +-- GET        hello/?name:String    Int
    +-- |]
    +-- @
    +--
    +-- Will generate:
    +--
    +-- @
    +--        "hello" :> ReqBody String :> Put ()
    +--   :\<|> "hello" :> Capture "p" Int :> ReqBody String :> Post ()
    +--   :\<|> "hello" :> QueryParam "name" String :> Get Int
    +-- @
    +--
    +-- Note the @/@ before a @QueryParam@!
    +module Servant.QQ where
    +
    +import Control.Monad (void)
    +import Control.Applicative hiding (many, (<|>), optional)
    +import Language.Haskell.TH.Quote
    +import Language.Haskell.TH
    +import Text.ParserCombinators.Parsec
    +
    +import Servant.API.Capture
    +import Servant.API.Get
    +import Servant.API.Post
    +import Servant.API.Put
    +import Servant.API.Delete
    +import Servant.API.QueryParam
    +import Servant.API.ReqBody
    +import Servant.API.Sub
    +import Servant.API.Alternative
    +
    +-- | Finally-tagless encoding for our DSL.
    +-- Keeping 'repr'' and 'repr' distinct when writing functions with an
    +-- @ExpSYM@ context ensures certain invariants (for instance, that there is
    +-- only one of 'get', 'post', 'put', and 'delete' in a value), but
    +-- sometimes requires a little more work.
    +class ExpSYM repr' repr | repr -> repr', repr' -> repr where
    +    lit        :: String -> repr' -> repr
    +    capture    :: String -> String -> repr -> repr
    +    reqBody    :: String -> repr -> repr
    +    queryParam :: String -> String -> repr -> repr
    +    conj       :: repr' -> repr -> repr
    +    get        :: String -> repr
    +    post       :: String -> repr
    +    put        :: String -> repr
    +    delete     :: String -> repr
    +
    +
    +infixr 6 >:
    +
    +(>:) :: Type -> Type -> Type
    +(>:) = conj
    +
    +
    +instance ExpSYM Type Type where
    +    lit name r         = LitT (StrTyLit name) >: r
    +    capture name typ r = AppT (AppT (ConT ''Capture) (LitT (StrTyLit name)))
    +                               (ConT $ mkName typ) >: r
    +    reqBody typ r      = AppT (ConT ''ReqBody) (ConT $ mkName typ) >: r
    +    queryParam name typ r = AppT (AppT (ConT ''QueryParam) (LitT (StrTyLit name)))
    +                               (ConT $ mkName typ) >: r
    +    conj x             = AppT (AppT (ConT ''(:>)) x)
    +    get  typ           = AppT (ConT ''Get) (ConT $ mkName typ)
    +    post typ           = AppT (ConT ''Post) (ConT $ mkName typ)
    +    put typ            = AppT (ConT ''Put) (ConT $ mkName typ)
    +    delete "()"        = ConT ''Delete
    +    delete _           = error "Delete does not return a request body"
    +
    +parseMethod :: ExpSYM repr' repr => Parser (String -> repr)
    +parseMethod = try (string "GET"    >> return get)
    +          <|> try (string "POST"   >> return post)
    +          <|> try (string "PUT"    >> return put)
    +          <|> try (string "DELETE" >> return delete)
    +
    +parseUrlSegment :: ExpSYM repr repr => Parser (repr -> repr)
    +parseUrlSegment = try parseCapture
    +              <|> try parseQueryParam
    +              <|> try parseLit
    +  where
    +      parseCapture = do
    +         cname <- many (noneOf " ?/:")
    +         char ':'
    +         ctyp  <- many (noneOf " ?/:")
    +         return $ capture cname ctyp
    +      parseQueryParam = do
    +         char '?'
    +         cname <- many (noneOf " ?/:")
    +         char ':'
    +         ctyp  <- many (noneOf " ?/:")
    +         return $ queryParam cname ctyp
    +      parseLit = lit <$> many (noneOf " ?/:")
    +
    +parseUrl :: ExpSYM repr repr => Parser (repr -> repr)
    +parseUrl = do
    +    optional $ char '/'
    +    url <- parseUrlSegment `sepBy1` char '/'
    +    return $ foldr1 (.) url
    +
    +data Typ = Val String
    +         | ReqArgVal String String
    +
    +parseTyp :: Parser Typ
    +parseTyp = do
    +    f <- many (noneOf "-{\n\r")
    +    spaces
    +    s <- optionMaybe (try parseRet)
    +    try $ optional inlineComment
    +    try $ optional blockComment
    +    case s of
    +        Nothing -> return $ Val (stripTr f)
    +        Just s' -> return $ ReqArgVal (stripTr f) (stripTr s')
    +  where
    +    parseRet :: Parser String
    +    parseRet = do
    +        string "->"
    +        spaces
    +        many (noneOf "-{\n\r")
    +    stripTr = reverse . dropWhile (== ' ') . reverse
    +
    +
    +parseEntry :: ExpSYM repr repr => Parser repr
    +parseEntry = do
    +    met <- parseMethod
    +    spaces
    +    url <- parseUrl
    +    spaces
    +    typ <- parseTyp
    +    case typ of
    +        Val s -> return $ url (met s)
    +        ReqArgVal i o -> return $ url $ reqBody i (met o)
    +
    +blockComment :: Parser ()
    +blockComment = do
    +    string "{-"
    +    manyTill anyChar (try $ string "-}")
    +    return ()
    +
    +inlineComment :: Parser ()
    +inlineComment = do
    +    string "--"
    +    manyTill anyChar (try $ lookAhead eol)
    +    return ()
    +
    +eol :: Parser String
    +eol =   try (string "\n\r")
    +    <|> try (string "\r\n")
    +    <|> string "\n"
    +    <|> string "\r"
    +    <?> "end of line"
    +
    +eols :: Parser ()
    +eols = skipMany $ void eol <|> blockComment <|> inlineComment
    +
    +parseAll :: Parser Type
    +parseAll = do
    +    eols
    +    entries <- parseEntry `endBy` eols
    +    return $ foldr1 union entries
    +  where union :: Type -> Type -> Type
    +        union a = AppT (AppT (ConT ''(:<|>)) a)
    +
    +-- | The sitemap QuasiQuoter.
    +--
    +--     * @.../<var>:<type>/...@ becomes a capture
    +--     * @.../?<var>:<type>@ becomes a query parameter
    +--     * @<method>   ...  <typ>@ becomes a method returning @<typ>@
    +--     * @<method>   ...  <typ1> -> <typ2>@ becomes a method with request
    +--       body of @<typ1>@ and returning @<typ2>@
    +--
    +-- Comments are allowed, and have the standard Haskell format
    +--
    +--     * @--@ for inline
    +--     * @{- ... -}@ for block
    +--
    +sitemap :: QuasiQuoter
    +sitemap = QuasiQuoter { quoteExp = undefined
    +                      , quotePat = undefined
    +                      , quoteType = \x -> case parse parseAll "" x of
    +                            Left err -> error $ show err
    +                            Right st -> return st
    +                      , quoteDec = undefined
    +                      }
    +
    +
    + diff --git a/src/Servant-Server.html b/src/Servant-Server.html new file mode 100644 index 00000000..95021c19 --- /dev/null +++ b/src/Servant-Server.html @@ -0,0 +1,116 @@ + + + + + +src/Servant/Server.hs + + + +
    {-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE OverloadedStrings #-}
    +
    +-- | This module lets you implement 'Server's for defined APIs. You'll
    +-- most likely just need 'serve'.
    +module Servant.Server where
    +
    +import Data.Monoid
    +import Data.Proxy
    +import Network.HTTP.Types
    +import Network.Wai
    +
    +-- * Implementing Servers
    +
    +-- | 'serve' allows you to implement an API and produce a wai 'Application'.
    +--
    +-- Example:
    +--
    +-- > type MyApi = "books" :> Get [Book] -- GET /books
    +-- >         :<|> "books" :> ReqBody Book :> Post Book -- POST /books
    +-- >
    +-- > server :: Server MyApi
    +-- > server = listAllBooks :<|> postBook
    +-- >   where listAllBooks = ...
    +-- >         postBook book = ...
    +-- >
    +-- > app :: Application
    +-- > app = serve myApi server
    +-- >
    +-- > main :: IO ()
    +-- > main = Network.Wai.Handler.Warp.run 8080 app
    +serve :: HasServer layout => Proxy layout -> Server layout -> Application
    +serve p server = toApplication (route p server)
    +
    +toApplication :: RoutingApplication -> Application
    +toApplication ra request respond = do
    +  ra request (routingRespond . routeResult)
    + where
    +  routingRespond :: Either RouteMismatch Response -> IO ResponseReceived
    +  routingRespond (Left NotFound) =
    +    respond $ responseLBS notFound404 [] "not found"
    +  routingRespond (Left WrongMethod) =
    +    respond $ responseLBS methodNotAllowed405 [] "method not allowed"
    +  routingRespond (Left InvalidBody) =
    +    respond $ responseLBS badRequest400 [] "Invalid JSON in request body"
    +  routingRespond (Right response) =
    +    respond response
    +
    +-- * Route mismatch
    +data RouteMismatch =
    +    NotFound    -- ^ the usual "not found" error
    +  | WrongMethod -- ^ a more informative "you just got the HTTP method wrong" error
    +  | InvalidBody -- ^ an even more informative "your json request body wasn't valid" error
    +  deriving (Eq, Show)
    +
    +-- | 
    +-- @
    +-- > mempty = NotFound
    +-- >
    +-- > NotFound    `mappend`           x = x
    +-- > WrongMethod `mappend` InvalidBody = InvalidBody
    +-- > WrongMethod `mappend`           _ = WrongMethod
    +-- > InvalidBody `mappend`           _ = InvalidBody
    +-- @
    +instance Monoid RouteMismatch where
    +  mempty = NotFound
    +
    +  NotFound    `mappend`           x = x
    +  WrongMethod `mappend` InvalidBody = InvalidBody
    +  WrongMethod `mappend`           _ = WrongMethod
    +  InvalidBody `mappend`           _ = InvalidBody
    +
    +-- | A wrapper around @'Either' 'RouteMismatch' a@.
    +newtype RouteResult a =
    +  RR { routeResult :: Either RouteMismatch a }
    +  deriving (Eq, Show)
    +
    +failWith :: RouteMismatch -> RouteResult a
    +failWith = RR . Left
    +
    +succeedWith :: a -> RouteResult a
    +succeedWith = RR . Right
    +
    +isMismatch :: RouteResult a -> Bool
    +isMismatch (RR (Left _)) = True
    +isMismatch _             = False
    +
    +-- | If we get a `Right`, it has precedence over everything else.
    +--
    +-- This in particular means that if we could get several 'Right's,
    +-- only the first we encounter would be taken into account.
    +instance Monoid (RouteResult a) where
    +  mempty = RR $ Left mempty
    +
    +  RR (Left x)  `mappend` RR (Left y)  = RR $ Left (x <> y)
    +  RR (Left _)  `mappend` RR (Right y) = RR $ Right y
    +  r            `mappend` _            = r
    +
    +type RoutingApplication =
    +     Request -- ^ the request, the field 'pathInfo' may be modified by url routing
    +  -> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived
    +
    +class HasServer layout where
    +  type Server layout :: *
    +  route :: Proxy layout -> Server layout -> RoutingApplication
    +
    + diff --git a/src/Servant-Utils-Links.html b/src/Servant-Utils-Links.html new file mode 100644 index 00000000..1884c23f --- /dev/null +++ b/src/Servant-Utils-Links.html @@ -0,0 +1,121 @@ + + + + + +src/Servant/Utils/Links.hs + + + +
    {-# LANGUAGE PolyKinds #-}
    +{-# LANGUAGE DataKinds #-}
    +{-# LANGUAGE FlexibleInstances #-}
    +{-# LANGUAGE TypeFamilies #-}
    +{-# LANGUAGE TypeOperators #-}
    +{-# LANGUAGE FunctionalDependencies #-}
    +{-# LANGUAGE ScopedTypeVariables #-}
    +{-# LANGUAGE UndecidableInstances #-}
    +-- | Type safe internal links.
    +--
    +-- Provides the function 'mkLink':
    +--
    +-- @
    +--   type API = Proxy ("hello" :> Get Int
    +--                :<|> "bye" :> QueryParam "name" String :> Post Bool)
    +--
    +--   api :: API
    +--   api = proxy
    +--
    +--   link1 :: Proxy ("hello" :> Get Int)
    +--   link1 = proxy
    +--
    +--   link2 :: Proxy ("hello" :> Delete)
    +--   link2 = proxy
    +--
    +--   mkLink link1 API  --  typechecks, returns 'Link "/hello"'
    +--
    +--   mkLink link2  API  -- doesn't typecheck
    +-- @
    +--
    +-- That is, 'mkLink' takes two arguments, a link proxy and a sitemap, and
    +-- returns a 'Link', but only typechecks if the link proxy is a valid link,
    +-- and part of the sitemap.
    +--
    +-- __N.B.:__ 'mkLink' assumes a capture matches any string (without slashes).
    +module Servant.Utils.Links where
    +
    +import Data.Proxy
    +import GHC.TypeLits
    +
    +import Servant.API.Capture
    +import Servant.API.ReqBody
    +import Servant.API.QueryParam
    +import Servant.API.Get
    +import Servant.API.Post
    +import Servant.API.Put
    +import Servant.API.Delete
    +import Servant.API.Sub
    +import Servant.API.Alternative
    +
    +
    +type family Or a b where
    +    Or 'False 'False = 'False
    +    Or 'True b       = 'True
    +    Or a 'True       = 'True
    +
    +type family And a b where
    +    And 'True 'True = 'True
    +    And a 'False    = 'False
    +    And 'False b    = 'False
    +
    +type family IsElem a s where
    +    IsElem e (sa :<|> sb)                = Or (IsElem e sa) (IsElem e sb)
    +    IsElem (e :> sa) (e :> sb)           = IsElem sa sb
    +    IsElem (e :> sa) (Capture x y :> sb) = IsElem sa sb
    +    IsElem sa (ReqBody x :> sb)          = IsElem sa sb
    +    IsElem sa (QueryParam x y :> sb)     = IsElem sa sb
    +    IsElem e e                           = 'True
    +    IsElem e a                           = 'False
    +
    +type family IsLink'' l where
    +    IsLink'' (e :> Get x)    = IsLink' e
    +    IsLink'' (e :> Post x)   = IsLink' e
    +    IsLink'' (e :> Put x)    = IsLink' e
    +    IsLink'' (e :> Delete)   = IsLink' e
    +    IsLink'' a               = 'False
    +
    +type family IsLink' e where
    +    IsLink' (f :: Symbol)  = 'True
    +
    +type family IsLink e where
    +    IsLink (a :> b)        = Or (And (IsLink' a) (IsLink'' b))
    +                                (IsLink'' (a :> b))
    +
    +
    +-- | The 'ValidLinkIn f s' constraint holds when 's' is an API that
    +-- contains 'f', and 'f' is a link.
    +class ValidLinkIn f s where
    +    mkLink :: f -> s -> Link  -- ^ This function will only typecheck if `f`
    +                              -- is an URI within `s`
    +
    +instance ( IsElem f s ~ 'True
    +         , IsLink f ~ 'True
    +         , VLinkHelper f) => ValidLinkIn f s where
    +    mkLink _ _ = Link (vlh (Proxy :: Proxy f))
    +
    +data Link = Link String deriving Show
    +
    +class VLinkHelper f where
    +    vlh :: forall proxy. proxy f -> String
    +
    +instance (KnownSymbol s, VLinkHelper e) => VLinkHelper (s :> e) where
    +    vlh _ = "/" ++ symbolVal (Proxy :: Proxy s) ++ vlh (Proxy :: Proxy e)
    +
    +instance VLinkHelper (Get x) where
    +    vlh _ = ""
    +
    +instance VLinkHelper (Post x) where
    +    vlh _ = ""
    +
    +
    + diff --git a/src/Servant-Utils-StaticFiles.html b/src/Servant-Utils-StaticFiles.html new file mode 100644 index 00000000..f6252105 --- /dev/null +++ b/src/Servant-Utils-StaticFiles.html @@ -0,0 +1,47 @@ + + + + + +src/Servant/Utils/StaticFiles.hs + + + +
    -- | This module defines a sever-side handler that lets you serve static files.
    +--
    +-- - 'serveDirectory' lets you serve anything that lives under a particular
    +--   directory on your filesystem.
    +module Servant.Utils.StaticFiles (
    +  serveDirectory,
    + ) where
    +
    +import Filesystem.Path.CurrentOS (decodeString)
    +import Network.Wai.Application.Static
    +import Servant.API.Raw
    +import Servant.Server
    +
    +-- | Serve anything under the specified directory as a 'Raw' endpoint.
    +--
    +-- @
    +-- type MyApi = "static" :> Raw
    +--
    +-- server :: Server MyApi
    +-- server = serveDirectory "\/var\/www"
    +-- @
    +--
    +-- would capture any request to @\/static\/\<something>@ and look for
    +-- @\<something>@ under @\/var\/www@.
    +--
    +-- It will do its best to guess the MIME type for that file, based on the extension,
    +-- and send an appropriate /Content-Type/ header if possible.
    +--
    +-- If your goal is to serve HTML, CSS and Javascript files that use the rest of the API
    +-- as a webapp backend, you will most likely not want the static files to be hidden
    +-- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectory'
    +-- handler in the last position, because /servant/ will try to match the handlers
    +-- in order.
    +serveDirectory :: FilePath -> Server Raw
    +serveDirectory documentRoot =
    +  staticApp (defaultFileServerSettings (decodeString (documentRoot ++ "/")))
    +
    + diff --git a/src/Servant.html b/src/Servant.html new file mode 100644 index 00000000..c1350019 --- /dev/null +++ b/src/Servant.html @@ -0,0 +1,35 @@ + + + + + +src/Servant.hs + + + +
    module Servant (
    +  -- | This module and its submodules can be used to define servant APIs. Note
    +  -- that these API definitions don't directly implement a server (or anything
    +  -- else).
    +  module Servant.API,
    +  -- | For implementing servers for servant APIs.
    +  module Servant.Server,
    +  -- | Using your types in request paths and query string parameters
    +  module Servant.Common.Text,
    +  -- | Utilities on top of the servant core
    +  module Servant.QQ,
    +  module Servant.Utils.Links,
    +  module Servant.Utils.StaticFiles,
    +  -- | Useful re-exports
    +  Proxy(..),
    +  ) where
    +
    +import Data.Proxy
    +import Servant.API
    +import Servant.Common.Text
    +import Servant.Server
    +import Servant.QQ
    +import Servant.Utils.Links
    +import Servant.Utils.StaticFiles
    +
    + diff --git a/src/hscolour.css b/src/hscolour.css new file mode 100644 index 00000000..c15919e9 --- /dev/null +++ b/src/hscolour.css @@ -0,0 +1,5 @@ +.hs-keyglyph, .hs-layout {color: red;} +.hs-keyword {color: blue;} +.hs-comment, .hs-comment a {color: green;} +.hs-str, .hs-chr {color: teal;} +.hs-keyword, .hs-conid, .hs-varid, .hs-conop, .hs-varop, .hs-num, .hs-cpp, .hs-sel, .hs-definition {} diff --git a/synopsis.png b/synopsis.png new file mode 100644 index 0000000000000000000000000000000000000000..85fb86ec84907bcc86531dc82871948ff4d471fa GIT binary patch literal 11327 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 B5rhB$ literal 0 HcmV?d00001