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 (angular defAngularOptions) (www </> "angular" </> "api.js")
writeJSForAPI testApi axios (www </> "axios" </> "api.js")
writeServiceJS (www </> "angular" </> "api.service.js") writeServiceJS (www </> "angular" </> "api.service.js")
-- setup a shared counter -- 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; } body { text-align: center; }
#counter { color: green; } #counter { color: green; }
#inc { margin: 0px 20px; background-color: green; color: white; } #inc { margin: 0px 20px; background-color: green; color: white; }
iframe { height: 20%; width: 80%}
</style> </style>
</head> </head>
<body> <body>
<iframe src="vanilla/" width="80%" height="25%"></iframe> <iframe src="vanilla/"></iframe>
<iframe src="jquery/" width="80%" height="25%"></iframe> <iframe src="jquery/"></iframe>
<iframe src="angular/" width="80%" height="25%"></iframe> <iframe src="angular/"></iframe>
<iframe src="angular/service.html" width="80%" height="25%"></iframe> <iframe src="angular/service.html"></iframe>
<iframe src="axios/"></iframe>
</body> </body>
</html> </html>

View file

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

View file

@ -100,6 +100,12 @@ module Servant.JS
, AngularOptions(..) , AngularOptions(..)
, defAngularOptions , defAngularOptions
, -- * Axios code generation
axios
, axiosWith
, AxiosOptions(..)
, defAxiosOptions
, -- * Misc. , -- * Misc.
listFromAPI listFromAPI
, javascript , javascript
@ -111,6 +117,7 @@ module Servant.JS
import Data.Proxy import Data.Proxy
import Servant.API import Servant.API
import Servant.JS.Angular import Servant.JS.Angular
import Servant.JS.Axios
import Servant.JS.Internal import Servant.JS.Internal
import Servant.JS.JQuery import Servant.JS.JQuery
import Servant.JS.Vanilla 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.Vanilla as JS
import qualified Servant.JS.JQuery as JQ import qualified Servant.JS.JQuery as JQ
import qualified Servant.JS.Angular as NG import qualified Servant.JS.Angular as NG
import qualified Servant.JS.Axios as AX
import Servant.JSSpec.CustomHeaders import Servant.JSSpec.CustomHeaders
type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool
@ -55,12 +56,14 @@ data TestNames = Vanilla
| JQueryCustom | JQueryCustom
| Angular | Angular
| AngularCustom | AngularCustom
| Axios
| AxiosCustom
deriving (Show, Eq) deriving (Show, Eq)
customOptions :: CommonGeneratorOptions customOptions :: CommonGeneratorOptions
customOptions = defCommonGeneratorOptions { customOptions = defCommonGeneratorOptions
successCallback = "okCallback", { successCallback = "okCallback"
errorCallback = "errorCallback" , errorCallback = "errorCallback"
} }
spec :: Spec spec :: Spec
@ -71,9 +74,35 @@ spec = describe "Servant.JQuery" $ do
generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions) generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions)
generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions) generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions) 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 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 :: TestNames -> Spec
angularSpec test = describe specLabel $ do angularSpec test = describe specLabel $ do
@ -92,8 +121,7 @@ angularSpec test = describe specLabel $ do
output jsText output jsText
jsText `shouldNotContain` "getsomething($http, " jsText `shouldNotContain` "getsomething($http, "
where where
specLabel = "generateJS(" ++ (show test) ++ ")" specLabel = "AngularJS(" ++ (show test) ++ ")"
--output = putStrLn
output _ = return () output _ = return ()
testName = "MyService" testName = "MyService"
ngOpts = NG.defAngularOptions { NG.serviceName = testName } ngOpts = NG.defAngularOptions { NG.serviceName = testName }