Merge pull request #173 from freezeboy/axios

servant-js: Add support for the Axios JS library
This commit is contained in:
Alp Mestanogullari 2015-07-29 14:10:41 +02:00
commit 3dc304b8d7
8 changed files with 230 additions and 11 deletions

View File

@ -92,6 +92,8 @@ main = do
writeJSForAPI testApi (angular defAngularOptions) (www </> "angular" </> "api.js")
writeJSForAPI testApi axios (www </> "axios" </> "api.js")
writeServiceJS (www </> "angular" </> "api.service.js")
-- setup a shared counter

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,40 @@
<html>
<head>
<title>Servant: counter</title>
<style>
body { text-align: center; }
#counter { color: green; }
#inc { margin: 0px 20px; background-color: green; color: white; }
</style>
</head>
<body>
<h1>Axios version</h1>
<span id="counter">Counter: 0</span>
<button id="inc">Increase</button>
<script src="axios.min.js" type="text/javascript"></script>
<script src="api.js" type="text/javascript"></script>
<script type="text/javascript">
window.addEventListener('load', function() {
// we get the current value stored by the server when the page is loaded
getCounter().then(updateCounter).catch(alert);
// we update the value every 1sec, in the same way
window.setInterval(function() {
getCounter().then(updateCounter).catch(alert);
}, 1000);
});
function updateCounter(response)
{
document.getElementById('counter').innerHTML = 'Counter: ' + response.data.value;
}
// when the button is clicked, ask the server to increase
// the value by one
document.getElementById('inc').addEventListener('click', function() {
postCounter().then(updateCounter).catch(alert);
});
</script>
</body>
</html>

View File

@ -5,13 +5,15 @@
body { text-align: center; }
#counter { color: green; }
#inc { margin: 0px 20px; background-color: green; color: white; }
iframe { height: 20%; width: 80%}
</style>
</head>
<body>
<iframe src="vanilla/" width="80%" height="25%"></iframe>
<iframe src="jquery/" width="80%" height="25%"></iframe>
<iframe src="angular/" width="80%" height="25%"></iframe>
<iframe src="angular/service.html" width="80%" height="25%"></iframe>
<iframe src="vanilla/"></iframe>
<iframe src="jquery/"></iframe>
<iframe src="angular/"></iframe>
<iframe src="angular/service.html"></iframe>
<iframe src="axios/"></iframe>
</body>
</html>

View File

@ -36,6 +36,7 @@ flag example
library
exposed-modules: Servant.JS
Servant.JS.Angular
Servant.JS.Axios
Servant.JS.JQuery
Servant.JS.Vanilla
Servant.JS.Internal

View File

@ -100,6 +100,12 @@ module Servant.JS
, AngularOptions(..)
, defAngularOptions
, -- * Axios code generation
axios
, axiosWith
, AxiosOptions(..)
, defAxiosOptions
, -- * Misc.
listFromAPI
, javascript
@ -111,6 +117,7 @@ module Servant.JS
import Data.Proxy
import Servant.API
import Servant.JS.Angular
import Servant.JS.Axios
import Servant.JS.Internal
import Servant.JS.JQuery
import Servant.JS.Vanilla

View File

@ -0,0 +1,129 @@
module Servant.JS.Axios where
import Servant.JS.Internal
import Control.Lens
import Data.Char (toLower)
import Data.List
import Data.Monoid
-- | Axios 'configuration' type
-- Let you customize the generation using Axios capabilities
data AxiosOptions = AxiosOptions
{ -- | indicates whether or not cross-site Access-Control requests
-- should be made using credentials
withCredentials :: !Bool
-- | the name of the cookie to use as a value for xsrf token
, xsrfCookieName :: !(Maybe String)
-- | the name of the header to use as a value for xsrf token
, xsrfHeaderName :: !(Maybe String)
}
-- | Default instance of the AxiosOptions
-- Defines the settings as they are in the Axios documentation
-- by default
defAxiosOptions :: AxiosOptions
defAxiosOptions = AxiosOptions
{ withCredentials = False
, xsrfCookieName = Nothing
, xsrfHeaderName = Nothing
}
-- | Generate regular javacript functions that use
-- the axios library, using default values for 'CommonGeneratorOptions'.
axios :: AxiosOptions -> JavaScriptGenerator
axios aopts = axiosWith aopts defCommonGeneratorOptions
-- | Generate regular javascript functions that use the axios library.
axiosWith :: AxiosOptions -> CommonGeneratorOptions -> JavaScriptGenerator
axiosWith aopts opts = intercalate "\n\n" . map (generateAxiosJSWith aopts opts)
-- | js codegen using axios library using default options
generateAxiosJS :: AxiosOptions -> AjaxReq -> String
generateAxiosJS aopts = generateAxiosJSWith aopts defCommonGeneratorOptions
-- | js codegen using axios library
generateAxiosJSWith :: AxiosOptions -> CommonGeneratorOptions -> AjaxReq -> String
generateAxiosJSWith aopts opts req = "\n" <>
fname <> " = function(" <> argsStr <> ")\n"
<> "{\n"
<> " return axios({ url: " <> url <> "\n"
<> " , method: '" <> method <> "'\n"
<> dataBody
<> reqheaders
<> withCreds
<> xsrfCookie
<> xsrfHeader
<> " });\n"
<> "}\n"
where argsStr = intercalate ", " args
args = captures
++ map (view argName) queryparams
++ body
++ map (toValidFunctionName . (<>) "header" . headerArgName) hs
captures = map captureArg
. filter isCapture
$ req ^. reqUrl.path
hs = req ^. reqHeaders
queryparams = req ^.. reqUrl.queryStr.traverse
body = if req ^. reqBody
then [requestBody opts]
else []
dataBody =
if req ^. reqBody
then " , data: body\n" <>
" , responseType: 'json'\n"
else ""
withCreds =
if withCredentials aopts
then " , withCredentials: true\n"
else ""
xsrfCookie =
case xsrfCookieName aopts of
Just name -> " , xsrfCookieName: '" <> name <> "'\n"
Nothing -> ""
xsrfHeader =
case xsrfHeaderName aopts of
Just name -> " , xsrfHeaderName: '" <> name <> "'\n"
Nothing -> ""
reqheaders =
if null hs
then ""
else " , headers: { " <> headersStr <> " }\n"
where headersStr = intercalate ", " $ map headerStr hs
headerStr header = "\"" ++
headerArgName header ++
"\": " ++ show header
namespace =
if hasNoModule
then "var "
else (moduleName opts) <> "."
where
hasNoModule = null (moduleName opts)
fname = namespace <> (functionNameBuilder opts $ req ^. funcName)
method = map toLower $ req ^. reqMethod
url = if url' == "'" then "'/'" else url'
url' = "'"
++ urlPrefix opts
++ urlArgs
++ queryArgs
urlArgs = jsSegments
$ req ^.. reqUrl.path.traverse
queryArgs = if null queryparams
then ""
else " + '?" ++ jsParams queryparams

View File

@ -17,6 +17,7 @@ import Servant.JS
import qualified Servant.JS.Vanilla as JS
import qualified Servant.JS.JQuery as JQ
import qualified Servant.JS.Angular as NG
import qualified Servant.JS.Axios as AX
import Servant.JSSpec.CustomHeaders
type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool
@ -55,13 +56,15 @@ data TestNames = Vanilla
| JQueryCustom
| Angular
| AngularCustom
| Axios
| AxiosCustom
deriving (Show, Eq)
customOptions :: CommonGeneratorOptions
customOptions = defCommonGeneratorOptions {
successCallback = "okCallback",
errorCallback = "errorCallback"
}
customOptions = defCommonGeneratorOptions
{ successCallback = "okCallback"
, errorCallback = "errorCallback"
}
spec :: Spec
spec = describe "Servant.JQuery" $ do
@ -71,9 +74,35 @@ spec = describe "Servant.JQuery" $ do
generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions)
generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { withCredentials = True }) customOptions)
angularSpec Angular
angularSpec AngularCustom
axiosSpec
--angularSpec AngularCustom
axiosSpec :: Spec
axiosSpec = describe specLabel $ do
it "should add withCredentials when needed" $ do
let jsText = genJS withCredOpts $ listFromAPI (Proxy :: Proxy TestAPI)
output jsText
jsText `shouldContain` ("withCredentials: true")
it "should add xsrfCookieName when needed" $ do
let jsText = genJS cookieOpts $ listFromAPI (Proxy :: Proxy TestAPI)
output jsText
jsText `shouldContain` ("xsrfCookieName: 'MyXSRFcookie'")
it "should add withCredentials when needed" $ do
let jsText = genJS headerOpts $ listFromAPI (Proxy :: Proxy TestAPI)
output jsText
jsText `shouldContain` ("xsrfHeaderName: 'MyXSRFheader'")
where
specLabel = "Axios"
output _ = return ()
withCredOpts = AX.defAxiosOptions { AX.withCredentials = True }
cookieOpts = AX.defAxiosOptions { AX.xsrfCookieName = Just "MyXSRFcookie" }
headerOpts = AX.defAxiosOptions { AX.xsrfHeaderName = Just "MyXSRFheader" }
genJS :: AxiosOptions -> [AjaxReq] -> String
genJS opts req = concat $ map (AX.generateAxiosJS opts) req
angularSpec :: TestNames -> Spec
angularSpec test = describe specLabel $ do
@ -92,8 +121,7 @@ angularSpec test = describe specLabel $ do
output jsText
jsText `shouldNotContain` "getsomething($http, "
where
specLabel = "generateJS(" ++ (show test) ++ ")"
--output = putStrLn
specLabel = "AngularJS(" ++ (show test) ++ ")"
output _ = return ()
testName = "MyService"
ngOpts = NG.defAngularOptions { NG.serviceName = testName }