diff --git a/servant-jquery/CHANGELOG.md b/servant-jquery/CHANGELOG.md new file mode 100644 index 00000000..99eeaf5f --- /dev/null +++ b/servant-jquery/CHANGELOG.md @@ -0,0 +1,12 @@ +0.3 +--- +* Extend `HeaderArg` to support more advanced HTTP header handling (https://github.com/haskell-servant/servant-jquery/pull/6) +* Support content-type aware combinators (but require that endpoints support JSON) +* Add support for Matrix params (https://github.com/haskell-servant/servant-jquery/pull/11) +* Add functions that directly generate the Javascript code from the API type without having to manually pattern match on the result. + +0.2.2 +----- + +* Fix an issue where toplevel Raw endpoints would generate a JS function with no name (https://github.com/haskell-servant/servant-jquery/issues/2) +* Replace dots by _ in paths (https://github.com/haskell-servant/servant-jquery/issues/1) diff --git a/servant-jquery/LICENSE b/servant-jquery/LICENSE new file mode 100644 index 00000000..bfee8018 --- /dev/null +++ b/servant-jquery/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2014, Zalora South East Asia Pte Ltd + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Zalora South East Asia Pte Ltd nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/servant-jquery/README.md b/servant-jquery/README.md new file mode 100644 index 00000000..e4b47216 --- /dev/null +++ b/servant-jquery/README.md @@ -0,0 +1,97 @@ +# servant-jquery + +[](http://travis-ci.org/haskell-servant/servant-jquery) +[](https://coveralls.io/r/haskell-servant/servant-jquery) + + + +This library lets you derive automatically (JQuery based) Javascript functions that let you query each endpoint of a *servant* webservice. + +## Example + +Read more about the following example [here](https://github.com/haskell-servant/servant-jquery/tree/master/examples#examples). + +``` haskell +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +import Control.Concurrent.STM +import Control.Monad.IO.Class +import Data.Aeson +import Data.Proxy +import GHC.Generics +import Network.Wai.Handler.Warp (run) +import Servant +import Servant.JQuery +import System.FilePath + +-- * A simple Counter data type +newtype Counter = Counter { value :: Int } + deriving (Generic, Show, Num) + +instance ToJSON Counter + +-- * Shared counter operations + +-- Creating a counter that starts from 0 +newCounter :: IO (TVar Counter) +newCounter = newTVarIO 0 + +-- Increasing the counter by 1 +counterPlusOne :: MonadIO m => TVar Counter -> m Counter +counterPlusOne counter = liftIO . atomically $ do + oldValue <- readTVar counter + let newValue = oldValue + 1 + writeTVar counter newValue + return newValue + +currentValue :: MonadIO m => TVar Counter -> m Counter +currentValue counter = liftIO $ readTVarIO counter + +-- * Our API type +type TestApi = "counter" :> Post Counter -- endpoint for increasing the counter + :<|> "counter" :> Get Counter -- endpoint to get the current value + :<|> Raw -- used for serving static files + +testApi :: Proxy TestApi +testApi = Proxy + +-- * Server-side handler + +-- where our static files reside +www :: FilePath +www = "examples/www" + +-- defining handlers +server :: TVar Counter -> Server TestApi +server counter = counterPlusOne counter -- (+1) on the TVar + :<|> currentValue counter -- read the TVar + :<|> serveDirectory www -- serve static files + +runServer :: TVar Counter -- ^ shared variable for the counter + -> Int -- ^ port the server should listen on + -> IO () +runServer var port = run port (serve testApi $ server var) + +-- * Generating the JQuery code + +incCounterJS :<|> currentValueJS :<|> _ = jquery testApi + +writeJS :: FilePath -> [AjaxReq] -> IO () +writeJS fp functions = writeFile fp $ + concatMap generateJS functions + +main :: IO () +main = do + -- write the JS code to www/api.js at startup + writeJS (www > "api.js") + [ incCounterJS, currentValueJS ] + + -- setup a shared counter + cnt <- newCounter + + -- listen to requests on port 8080 + runServer cnt 8080 +``` \ No newline at end of file diff --git a/servant-jquery/Setup.hs b/servant-jquery/Setup.hs new file mode 100644 index 00000000..9a994af6 --- /dev/null +++ b/servant-jquery/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/servant-jquery/TODO.md b/servant-jquery/TODO.md new file mode 100644 index 00000000..8ee2b547 --- /dev/null +++ b/servant-jquery/TODO.md @@ -0,0 +1 @@ +- Investigate the best way to offer cross-origin requests \ No newline at end of file diff --git a/servant-jquery/docs.sh b/servant-jquery/docs.sh new file mode 100644 index 00000000..58844b4f --- /dev/null +++ b/servant-jquery/docs.sh @@ -0,0 +1,52 @@ +SERVANT_DIR=/tmp/servant-jquery-gh-pages + +# Make a temporary clone + +rm -rf $SERVANT_DIR + +git clone . $SERVANT_DIR + +cd $SERVANT_DIR + +# Make sure to pull the latest + +git remote add haskell-servant git@github.com:haskell-servant/servant-jquery.git + +git fetch haskell-servant + +git reset --hard haskell-servant/gh-pages + +# Clear everything away + +git rm -rf $SERVANT_DIR/* + +# Switch back and build the haddocks + +cd - + +cabal configure --builddir=$SERVANT_DIR + +cabal haddock --hoogle --hyperlink-source --html-location='https://hackage.haskell.org/package/$pkg-$version/docs' --builddir=$SERVANT_DIR + +commit_hash=$(git rev-parse HEAD) + +# Move the HTML docs to the root + +cd $SERVANT_DIR + +rm * +rm -rf build +mv doc/html/servant-jquery/* . +rm -r doc/ + +# Add everything + +git add . + +git commit -m "Built from $commit_hash" + +# Push to update the pages + +git push haskell-servant HEAD:gh-pages + +rm -rf $SERVANT_DIR diff --git a/servant-jquery/examples/README.md b/servant-jquery/examples/README.md new file mode 100644 index 00000000..9e469882 --- /dev/null +++ b/servant-jquery/examples/README.md @@ -0,0 +1,17 @@ +# Examples + +## counter + +This example demonstrates a *servant* server that holds a shared variable (using a `TVar`) and exposes an endpoint for reading its current value and another one for increasing its current value by 1. + +In addition to that, it shows how you can generate the jquery-powered javascript functions corresponding to each endpoint, i.e one for reading the current value and one for increasing the value, and integrates all of that in a very simple HTML page. All these static files are served using the `serveDirectory` function from *servant*. + +To see this all in action, simply run: + +``` bash +$ cabal run counter +``` + +And point your browser to [http://localhost:8080/index.html](http://localhost:8080/index.html). + +Copies of the generated javascript functions and of the generated docs are included in `www/api.js` and `counter.md` respectively. \ No newline at end of file diff --git a/servant-jquery/examples/counter.hs b/servant-jquery/examples/counter.hs new file mode 100644 index 00000000..d5b6b156 --- /dev/null +++ b/servant-jquery/examples/counter.hs @@ -0,0 +1,82 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +import Control.Concurrent.STM +import Control.Monad.IO.Class +import Data.Aeson +import Data.Proxy +import GHC.Generics +import Network.Wai.Handler.Warp (run) +import Servant +import Servant.JQuery +import System.FilePath + +-- * A simple Counter data type +newtype Counter = Counter { value :: Int } + deriving (Generic, Show, Num) + +instance ToJSON Counter + +-- * Shared counter operations + +-- Creating a counter that starts from 0 +newCounter :: IO (TVar Counter) +newCounter = newTVarIO 0 + +-- Increasing the counter by 1 +counterPlusOne :: MonadIO m => TVar Counter -> m Counter +counterPlusOne counter = liftIO . atomically $ do + oldValue <- readTVar counter + let newValue = oldValue + 1 + writeTVar counter newValue + return newValue + +currentValue :: MonadIO m => TVar Counter -> m Counter +currentValue counter = liftIO $ readTVarIO counter + +-- * Our API type +type TestApi = "counter" :> Post Counter -- endpoint for increasing the counter + :<|> "counter" :> Get Counter -- endpoint to get the current value + :<|> Raw -- used for serving static files + +testApi :: Proxy TestApi +testApi = Proxy + +-- * Server-side handler + +-- where our static files reside +www :: FilePath +www = "examples/www" + +-- defining handlers +server :: TVar Counter -> Server TestApi +server counter = counterPlusOne counter -- (+1) on the TVar + :<|> currentValue counter -- read the TVar + :<|> serveDirectory www -- serve static files + +runServer :: TVar Counter -- ^ shared variable for the counter + -> Int -- ^ port the server should listen on + -> IO () +runServer var port = run port (serve testApi $ server var) + +-- * Generating the JQuery code + +incCounterJS :<|> currentValueJS :<|> _ = jquery testApi + +writeJS :: FilePath -> [AjaxReq] -> IO () +writeJS fp functions = writeFile fp $ + concatMap generateJS functions + +main :: IO () +main = do + -- write the JS code to www/api.js at startup + writeJS (www > "api.js") + [ incCounterJS, currentValueJS ] + + -- setup a shared counter + cnt <- newCounter + + -- listen to requests on port 8080 + runServer cnt 8080 diff --git a/servant-jquery/examples/counter.md b/servant-jquery/examples/counter.md new file mode 100644 index 00000000..92214d57 --- /dev/null +++ b/servant-jquery/examples/counter.md @@ -0,0 +1,39 @@ +POST /counter +------------- + +**Response**: + + - Status code 201 + - Response body as below. + +``` javascript +{"value":0} +``` + +GET /doc +-------- + +**Response**: + + - Status code 200 + - No response body + +GET /counter +------------ + +**Response**: + + - Status code 200 + - Response body as below. + +``` javascript +{"value":0} +``` + +GET / +----- + +**Response**: + + - Status code 200 + - No response body diff --git a/servant-jquery/examples/www/api.js b/servant-jquery/examples/www/api.js new file mode 100644 index 00000000..0adbd89a --- /dev/null +++ b/servant-jquery/examples/www/api.js @@ -0,0 +1,20 @@ + +function postcounter(onSuccess, onError) +{ + $.ajax( + { url: '/counter' + , success: onSuccess + , error: onError + , type: 'POST' + }); +} + +function getcounter(onSuccess, onError) +{ + $.ajax( + { url: '/counter' + , success: onSuccess + , error: onError + , type: 'GET' + }); +} diff --git a/servant-jquery/examples/www/index.html b/servant-jquery/examples/www/index.html new file mode 100644 index 00000000..075e6796 --- /dev/null +++ b/servant-jquery/examples/www/index.html @@ -0,0 +1,40 @@ + +
+t |