Merge pull request #173 from freezeboy/axios
servant-js: Add support for the Axios JS library
This commit is contained in:
commit
3dc304b8d7
8 changed files with 230 additions and 11 deletions
|
@ -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
|
||||||
|
|
10
servant-js/examples/www/axios/axios.min.js
vendored
Normal file
10
servant-js/examples/www/axios/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
40
servant-js/examples/www/axios/index.html
Normal file
40
servant-js/examples/www/axios/index.html
Normal 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>
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
129
servant-js/src/Servant/JS/Axios.hs
Normal file
129
servant-js/src/Servant/JS/Axios.hs
Normal 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
|
|
@ -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,13 +56,15 @@ 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
|
||||||
spec = describe "Servant.JQuery" $ do
|
spec = describe "Servant.JQuery" $ do
|
||||||
|
@ -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 }
|
||||||
|
|
Loading…
Reference in a new issue