on master help sign in
Merge pull request #173 from freezeboy/axios

servant-js: Add support for the Axios JS library

commit3dc304b8d7c9dbf2fb57afbc1d687f1ec8b48106 authorAlp Mestanogullari <alpmestan@gmail.com> dateWed 29 Jul 2015, 14:10 (11 years ago) parentdc902e24cab814
8 files changed+230−11
servant-js/examples/counter.hs+2 servant-js/examples/www/axios/axios.min.js+10 servant-js/examples/www/axios/index.html+40 servant-js/examples/www/index.html+6−4 servant-js/servant-js.cabal+1 servant-js/src/Servant/JS.hs+7 servant-js/src/Servant/JS/Axios.hs+129 servant-js/test/Servant/JSSpec.hs+35−7
servant-js/examples/counter.hs+2
@@ -92,6 +92,8 @@ main = do
9292
9393 writeJSForAPI testApi (angular defAngularOptions) (www </> "angular" </> "api.js")
9494
95+ writeJSForAPI testApi axios (www </> "axios" </> "api.js")
96+
9597 writeServiceJS (www </> "angular" </> "api.service.js")
9698
9799 -- setup a shared counter
servant-js/examples/www/axios/axios.min.jsnew file+10
@@ -0,0 +1,10 @@
1+/* axios v0.5.4 | (c) 2015 by Matt Zabriskie */
2+var axios=function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){t.exports=n(1)},function(t,e,n){"use strict";var r=n(2),o=n(3),i=n(4),s=n(5),u=n(6);!function(){var t=n(9);t&&"function"==typeof t.polyfill&&t.polyfill()}();var a=t.exports=function c(t){t=o.merge({method:"get",headers:{},transformRequest:r.transformRequest,transformResponse:r.transformResponse},t),t.withCredentials=t.withCredentials||r.withCredentials;var e=[s,void 0],n=Promise.resolve(t);for(c.interceptors.request.forEach(function(t){e.unshift(t.fulfilled,t.rejected)}),c.interceptors.response.forEach(function(t){e.push(t.fulfilled,t.rejected)});e.length;)n=n.then(e.shift(),e.shift());return n.success=function(t){return i("success","then","https://github.com/mzabriskie/axios/blob/master/README.md#response-api"),n.then(function(e){t(e.data,e.status,e.headers,e.config)}),n},n.error=function(t){return i("error","catch","https://github.com/mzabriskie/axios/blob/master/README.md#response-api"),n.then(null,function(e){t(e.data,e.status,e.headers,e.config)}),n},n};a.defaults=r,a.all=function(t){return Promise.all(t)},a.spread=n(7),a.interceptors={request:new u,response:new u},function(){function t(){o.forEach(arguments,function(t){a[t]=function(e,n){return a(o.merge(n||{},{method:t,url:e}))}})}function e(){o.forEach(arguments,function(t){a[t]=function(e,n,r){return a(o.merge(r||{},{method:t,url:e,data:n}))}})}t("delete","get","head"),e("post","put","patch")}()},function(t,e,n){"use strict";var r=n(3),o=/^\)\]\}',?\n/,i={"Content-Type":"application/x-www-form-urlencoded"};t.exports={transformRequest:[function(t,e){return r.isFormData(t)?t:r.isArrayBuffer(t)?t:r.isArrayBufferView(t)?t.buffer:!r.isObject(t)||r.isFile(t)||r.isBlob(t)?t:(!r.isUndefined(e)&&r.isUndefined(e["Content-Type"])&&(e["Content-Type"]="application/json;charset=utf-8"),JSON.stringify(t))}],transformResponse:[function(t){if("string"==typeof t){t=t.replace(o,"");try{t=JSON.parse(t)}catch(e){}}return t}],headers:{common:{Accept:"application/json, text/plain, */*"},patch:r.merge(i),post:r.merge(i),put:r.merge(i)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"}},function(t){"use strict";function e(t){return"[object Array]"===m.call(t)}function n(t){return"[object ArrayBuffer]"===m.call(t)}function r(t){return"[object FormData]"===m.call(t)}function o(t){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer&&t.buffer instanceof ArrayBuffer}function i(t){return"string"==typeof t}function s(t){return"number"==typeof t}function u(t){return"undefined"==typeof t}function a(t){return null!==t&&"object"==typeof t}function c(t){return"[object Date]"===m.call(t)}function f(t){return"[object File]"===m.call(t)}function l(t){return"[object Blob]"===m.call(t)}function p(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}function h(t,n){if(null!==t&&"undefined"!=typeof t){var r=e(t)||"object"==typeof t&&!isNaN(t.length);if("object"==typeof t||r||(t=[t]),r)for(var o=0,i=t.length;i>o;o++)n.call(null,t[o],o,t);else for(var s in t)t.hasOwnProperty(s)&&n.call(null,t[s],s,t)}}function d(){var t={};return h(arguments,function(e){h(e,function(e,n){t[n]=e})}),t}var m=Object.prototype.toString;t.exports={isArray:e,isArrayBuffer:n,isFormData:r,isArrayBufferView:o,isString:i,isNumber:s,isObject:a,isUndefined:u,isDate:c,isFile:f,isBlob:l,forEach:h,merge:d,trim:p}},function(t){"use strict";t.exports=function(t,e,n){try{console.warn("DEPRECATED method `"+t+"`."+(e?" Use `"+e+"` instead.":"")+" This method will be removed in a future release."),n&&console.warn("For more information about usage see "+n)}catch(r){}}},function(t,e,n){(function(e){"use strict";t.exports=function(t){return new Promise(function(r,o){try{"undefined"!=typeof window?n(8)(r,o,t):"undefined"!=typeof e&&n(8)(r,o,t)}catch(i){o(i)}})}}).call(e,n(10))},function(t,e,n){"use strict";function r(){this.handlers=[]}var o=n(3);r.prototype.use=function(t,e){return this.handlers.push({fulfilled:t,rejected:e}),this.handlers.length-1},r.prototype.eject=function(t){this.handlers[t]&&(this.handlers[t]=null)},r.prototype.forEach=function(t){o.forEach(this.handlers,function(e){null!==e&&t(e)})},t.exports=r},function(t){"use strict";t.exports=function(t){return function(e){t.apply(null,e)}}},function(t,e,n){"use strict";var r=n(2),o=n(3),i=n(11),s=n(12),u=n(13),a=n(14),c=n(15);t.exports=function(t,e,n){var f=a(n.data,n.headers,n.transformRequest),l=o.merge(r.headers.common,r.headers[n.method]||{},n.headers||{});o.isFormData(f)&&delete l["Content-Type"];var p=new(XMLHttpRequest||ActiveXObject)("Microsoft.XMLHTTP");p.open(n.method.toUpperCase(),i(n.url,n.params),!0),p.onreadystatechange=function(){if(p&&4===p.readyState){var r=u(p.getAllResponseHeaders()),o=-1!==["text",""].indexOf(n.responseType||"")?p.responseText:p.response,i={data:a(o,r,n.transformResponse),status:p.status,statusText:p.statusText,headers:r,config:n};(p.status>=200&&p.status<300?t:e)(i),p=null}};var h=c(n.url)?s.read(n.xsrfCookieName||r.xsrfCookieName):void 0;if(h&&(l[n.xsrfHeaderName||r.xsrfHeaderName]=h),o.forEach(l,function(t,e){f||"content-type"!==e.toLowerCase()?p.setRequestHeader(e,t):delete l[e]}),n.withCredentials&&(p.withCredentials=!0),n.responseType)try{p.responseType=n.responseType}catch(d){if("json"!==p.responseType)throw d}o.isArrayBuffer(f)&&(f=new DataView(f)),p.send(f)}},function(t,e,n){var r;(function(t,o,i){/*!
3+ * @overview es6-promise - a tiny implementation of Promises/A+.
4+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
5+ * @license Licensed under MIT license
6+ * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
7+ * @version 2.0.1
8+ */
9+(function(){"use strict";function s(t){return"function"==typeof t||"object"==typeof t&&null!==t}function u(t){return"function"==typeof t}function a(t){return"object"==typeof t&&null!==t}function c(){}function f(){return function(){t.nextTick(d)}}function l(){var t=0,e=new X(d),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function p(){var t=new MessageChannel;return t.port1.onmessage=d,function(){t.port2.postMessage(0)}}function h(){return function(){setTimeout(d,1)}}function d(){for(var t=0;H>t;t+=2){var e=J[t],n=J[t+1];e(n),J[t]=void 0,J[t+1]=void 0}H=0}function m(){}function y(){return new TypeError("You cannot resolve a promise with itself")}function v(){return new TypeError("A promises callback cannot return that same promise.")}function w(t){try{return t.then}catch(e){return z.error=e,z}}function g(t,e,n,r){try{t.call(e,n,r)}catch(o){return o}}function b(t,e,n){L(function(t){var r=!1,o=g(n,e,function(n){r||(r=!0,e!==n?A(t,n):j(t,n))},function(e){r||(r=!0,T(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&o&&(r=!0,T(t,o))},t)}function _(t,e){e._state===Y?j(t,e._result):t._state===$?T(t,e._result):C(e,void 0,function(e){A(t,e)},function(e){T(t,e)})}function x(t,e){if(e.constructor===t.constructor)_(t,e);else{var n=w(e);n===z?T(t,z.error):void 0===n?j(t,e):u(n)?b(t,e,n):j(t,e)}}function A(t,e){t===e?T(t,y()):s(e)?x(t,e):j(t,e)}function E(t){t._onerror&&t._onerror(t._result),R(t)}function j(t,e){t._state===K&&(t._result=e,t._state=Y,0===t._subscribers.length||L(R,t))}function T(t,e){t._state===K&&(t._state=$,t._result=e,L(E,t))}function C(t,e,n,r){var o=t._subscribers,i=o.length;t._onerror=null,o[i]=e,o[i+Y]=n,o[i+$]=r,0===i&&t._state&&L(R,t)}function R(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,o,i=t._result,s=0;s<e.length;s+=3)r=e[s],o=e[s+n],r?P(n,r,o,i):o(i);t._subscribers.length=0}}function S(){this.error=null}function O(t,e){try{return t(e)}catch(n){return G.error=n,G}}function P(t,e,n,r){var o,i,s,a,c=u(n);if(c){if(o=O(n,r),o===G?(a=!0,i=o.error,o=null):s=!0,e===o)return void T(e,v())}else o=r,s=!0;e._state!==K||(c&&s?A(e,o):a?T(e,i):t===Y?j(e,o):t===$&&T(e,o))}function k(t,e){try{e(function(e){A(t,e)},function(e){T(t,e)})}catch(n){T(t,n)}}function D(t,e,n,r){this._instanceConstructor=t,this.promise=new t(m,r),this._abortOnReject=n,this._validateInput(e)?(this._input=e,this.length=e.length,this._remaining=e.length,this._init(),0===this.length?j(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&j(this.promise,this._result))):T(this.promise,this._validationError())}function N(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function B(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function M(t){this._id=ne++,this._state=void 0,this._result=void 0,this._subscribers=[],m!==t&&(u(t)||N(),this instanceof M||B(),k(this,t))}var F;F=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var U,q=F,H=(Date.now||function(){return(new Date).getTime()},Object.create||function(t){if(arguments.length>1)throw new Error("Second argument not supported");if("object"!=typeof t)throw new TypeError("Argument must be an object");return c.prototype=t,new c},0),L=function(t,e){J[H]=t,J[H+1]=e,H+=2,2===H&&U()},I="undefined"!=typeof window?window:{},X=I.MutationObserver||I.WebKitMutationObserver,V="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,J=new Array(1e3);U="undefined"!=typeof t&&"[object process]"==={}.toString.call(t)?f():X?l():V?p():h();var K=void 0,Y=1,$=2,z=new S,G=new S;D.prototype._validateInput=function(t){return q(t)},D.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},D.prototype._init=function(){this._result=new Array(this.length)};var W=D;D.prototype._enumerate=function(){for(var t=this.length,e=this.promise,n=this._input,r=0;e._state===K&&t>r;r++)this._eachEntry(n[r],r)},D.prototype._eachEntry=function(t,e){var n=this._instanceConstructor;a(t)?t.constructor===n&&t._state!==K?(t._onerror=null,this._settledAt(t._state,e,t._result)):this._willSettleAt(n.resolve(t),e):(this._remaining--,this._result[e]=this._makeResult(Y,e,t))},D.prototype._settledAt=function(t,e,n){var r=this.promise;r._state===K&&(this._remaining--,this._abortOnReject&&t===$?T(r,n):this._result[e]=this._makeResult(t,e,n)),0===this._remaining&&j(r,this._result)},D.prototype._makeResult=function(t,e,n){return n},D.prototype._willSettleAt=function(t,e){var n=this;C(t,void 0,function(t){n._settledAt(Y,e,t)},function(t){n._settledAt($,e,t)})};var Q=function(t,e){return new W(this,t,!0,e).promise},Z=function(t,e){function n(t){A(i,t)}function r(t){T(i,t)}var o=this,i=new o(m,e);if(!q(t))return T(i,new TypeError("You must pass an array to race.")),i;for(var s=t.length,u=0;i._state===K&&s>u;u++)C(o.resolve(t[u]),void 0,n,r);return i},te=function(t,e){var n=this;if(t&&"object"==typeof t&&t.constructor===n)return t;var r=new n(m,e);return A(r,t),r},ee=function(t,e){var n=this,r=new n(m,e);return T(r,t),r},ne=0,re=M;M.all=Q,M.race=Z,M.resolve=te,M.reject=ee,M.prototype={constructor:M,then:function(t,e){var n=this,r=n._state;if(r===Y&&!t||r===$&&!e)return this;var o=new this.constructor(m),i=n._result;if(r){var s=arguments[r-1];L(function(){P(r,o,s,i)})}else C(n,o,t,e);return o},"catch":function(t){return this.then(null,t)}};var oe=function(){var t;t="undefined"!=typeof o?o:"undefined"!=typeof window&&window.document?window:self;var e="Promise"in t&&"resolve"in t.Promise&&"reject"in t.Promise&&"all"in t.Promise&&"race"in t.Promise&&function(){var e;return new t.Promise(function(t){e=t}),u(e)}();e||(t.Promise=re)},ie={Promise:re,polyfill:oe};n(16).amd?(r=function(){return ie}.call(e,n,e,i),!(void 0!==r&&(i.exports=r))):"undefined"!=typeof i&&i.exports?i.exports=ie:"undefined"!=typeof this&&(this.ES6Promise=ie)}).call(this)}).call(e,n(10),function(){return this}(),n(17)(t))},function(t){function e(){if(!i){i=!0;for(var t,e=o.length;e;){t=o,o=[];for(var n=-1;++n<e;)t[n]();e=o.length}i=!1}}function n(){}var r=t.exports={},o=[],i=!1;r.nextTick=function(t){o.push(t),i||setTimeout(e,0)},r.title="browser",r.browser=!0,r.env={},r.argv=[],r.version="",r.versions={},r.on=n,r.addListener=n,r.once=n,r.off=n,r.removeListener=n,r.removeAllListeners=n,r.emit=n,r.binding=function(){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(){throw new Error("process.chdir is not supported")},r.umask=function(){return 0}},function(t,e,n){"use strict";function r(t){return encodeURIComponent(t).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}var o=n(3);t.exports=function(t,e){if(!e)return t;var n=[];return o.forEach(e,function(t,e){null!==t&&"undefined"!=typeof t&&(o.isArray(t)||(t=[t]),o.forEach(t,function(t){o.isDate(t)?t=t.toISOString():o.isObject(t)&&(t=JSON.stringify(t)),n.push(r(e)+"="+r(t))}))}),n.length>0&&(t+=(-1===t.indexOf("?")?"?":"&")+n.join("&")),t}},function(t,e,n){"use strict";var r=n(3);t.exports={write:function(t,e,n,o,i,s){var u=[];u.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&u.push("expires="+new Date(n).toGMTString()),r.isString(o)&&u.push("path="+o),r.isString(i)&&u.push("domain="+i),s===!0&&u.push("secure"),document.cookie=u.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}},function(t,e,n){"use strict";var r=n(3);t.exports=function(t){var e,n,o,i={};return t?(r.forEach(t.split("\n"),function(t){o=t.indexOf(":"),e=r.trim(t.substr(0,o)).toLowerCase(),n=r.trim(t.substr(o+1)),e&&(i[e]=i[e]?i[e]+", "+n:n)}),i):i}},function(t,e,n){"use strict";var r=n(3);t.exports=function(t,e,n){return r.forEach(n,function(n){t=n(t,e)}),t}},function(t,e,n){"use strict";function r(t){var e=t;return s&&(u.setAttribute("href",e),e=u.href),u.setAttribute("href",e),{href:u.href,protocol:u.protocol?u.protocol.replace(/:$/,""):"",host:u.host,search:u.search?u.search.replace(/^\?/,""):"",hash:u.hash?u.hash.replace(/^#/,""):"",hostname:u.hostname,port:u.port,pathname:"/"===u.pathname.charAt(0)?u.pathname:"/"+u.pathname}}var o,i=n(3),s=/(msie|trident)/i.test(navigator.userAgent),u=document.createElement("a");o=r(window.location.href),t.exports=function(t){var e=i.isString(t)?r(t):t;return e.protocol===o.protocol&&e.host===o.host}},function(t){t.exports=function(){throw new Error("define cannot be used indirect")}},function(t){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}}]);
10+//# sourceMappingURL=axios.min.map
servant-js/examples/www/axios/index.htmlnew file+40
@@ -0,0 +1,40 @@
1+<html>
2+<head>
3+ <title>Servant: counter</title>
4+ <style>
5+ body { text-align: center; }
6+ #counter { color: green; }
7+ #inc { margin: 0px 20px; background-color: green; color: white; }
8+ </style>
9+</head>
10+<body>
11+<h1>Axios version</h1>
12+<span id="counter">Counter: 0</span>
13+<button id="inc">Increase</button>
14+
15+<script src="axios.min.js" type="text/javascript"></script>
16+<script src="api.js" type="text/javascript"></script>
17+<script type="text/javascript">
18+window.addEventListener('load', function() {
19+ // we get the current value stored by the server when the page is loaded
20+ getCounter().then(updateCounter).catch(alert);
21+
22+ // we update the value every 1sec, in the same way
23+ window.setInterval(function() {
24+ getCounter().then(updateCounter).catch(alert);
25+ }, 1000);
26+});
27+
28+function updateCounter(response)
29+{
30+ document.getElementById('counter').innerHTML = 'Counter: ' + response.data.value;
31+}
32+
33+// when the button is clicked, ask the server to increase
34+// the value by one
35+document.getElementById('inc').addEventListener('click', function() {
36+ postCounter().then(updateCounter).catch(alert);
37+});
38+</script>
39+</body>
40+</html>
servant-js/examples/www/index.html+6−4
@@ -5,13 +5,15 @@
55 body { text-align: center; }
66 #counter { color: green; }
77 #inc { margin: 0px 20px; background-color: green; color: white; }
8+ iframe { height: 20%; width: 80%}
89 </style>
910</head>
1011<body>
1112
12−<iframe src="vanilla/" width="80%" height="25%"></iframe>
13−<iframe src="jquery/" width="80%" height="25%"></iframe>
14−<iframe src="angular/" width="80%" height="25%"></iframe>
15−<iframe src="angular/service.html" width="80%" height="25%"></iframe>
13+<iframe src="vanilla/"></iframe>
14+<iframe src="jquery/"></iframe>
15+<iframe src="angular/"></iframe>
16+<iframe src="angular/service.html"></iframe>
17+<iframe src="axios/"></iframe>
1618</body>
1719</html>
servant-js/servant-js.cabal+1
@@ -36,6 +36,7 @@ flag example
3636library
3737 exposed-modules: Servant.JS
3838 Servant.JS.Angular
39+ Servant.JS.Axios
3940 Servant.JS.JQuery
4041 Servant.JS.Vanilla
4142 Servant.JS.Internal
servant-js/src/Servant/JS.hs+7
@@ -100,6 +100,12 @@ module Servant.JS
100100 , AngularOptions(..)
101101 , defAngularOptions
102102
103+ , -- * Axios code generation
104+ axios
105+ , axiosWith
106+ , AxiosOptions(..)
107+ , defAxiosOptions
108+
103109 , -- * Misc.
104110 listFromAPI
105111 , javascript
@@ -111,6 +117,7 @@ module Servant.JS
111117import Data.Proxy
112118import Servant.API
113119import Servant.JS.Angular
120+import Servant.JS.Axios
114121import Servant.JS.Internal
115122import Servant.JS.JQuery
116123import Servant.JS.Vanilla
servant-js/src/Servant/JS/Axios.hsnew file+129
@@ -0,0 +1,129 @@
1+module Servant.JS.Axios where
2+
3+import Servant.JS.Internal
4+import Control.Lens
5+import Data.Char (toLower)
6+import Data.List
7+import Data.Monoid
8+
9+-- | Axios 'configuration' type
10+-- Let you customize the generation using Axios capabilities
11+data AxiosOptions = AxiosOptions
12+ { -- | indicates whether or not cross-site Access-Control requests
13+ -- should be made using credentials
14+ withCredentials :: !Bool
15+ -- | the name of the cookie to use as a value for xsrf token
16+ , xsrfCookieName :: !(Maybe String)
17+ -- | the name of the header to use as a value for xsrf token
18+ , xsrfHeaderName :: !(Maybe String)
19+ }
20+
21+-- | Default instance of the AxiosOptions
22+-- Defines the settings as they are in the Axios documentation
23+-- by default
24+defAxiosOptions :: AxiosOptions
25+defAxiosOptions = AxiosOptions
26+ { withCredentials = False
27+ , xsrfCookieName = Nothing
28+ , xsrfHeaderName = Nothing
29+ }
30+
31+-- | Generate regular javacript functions that use
32+-- the axios library, using default values for 'CommonGeneratorOptions'.
33+axios :: AxiosOptions -> JavaScriptGenerator
34+axios aopts = axiosWith aopts defCommonGeneratorOptions
35+
36+-- | Generate regular javascript functions that use the axios library.
37+axiosWith :: AxiosOptions -> CommonGeneratorOptions -> JavaScriptGenerator
38+axiosWith aopts opts = intercalate "\n\n" . map (generateAxiosJSWith aopts opts)
39+
40+-- | js codegen using axios library using default options
41+generateAxiosJS :: AxiosOptions -> AjaxReq -> String
42+generateAxiosJS aopts = generateAxiosJSWith aopts defCommonGeneratorOptions
43+
44+-- | js codegen using axios library
45+generateAxiosJSWith :: AxiosOptions -> CommonGeneratorOptions -> AjaxReq -> String
46+generateAxiosJSWith aopts opts req = "\n" <>
47+ fname <> " = function(" <> argsStr <> ")\n"
48+ <> "{\n"
49+ <> " return axios({ url: " <> url <> "\n"
50+ <> " , method: '" <> method <> "'\n"
51+ <> dataBody
52+ <> reqheaders
53+ <> withCreds
54+ <> xsrfCookie
55+ <> xsrfHeader
56+ <> " });\n"
57+ <> "}\n"
58+
59+ where argsStr = intercalate ", " args
60+ args = captures
61+ ++ map (view argName) queryparams
62+ ++ body
63+ ++ map (toValidFunctionName . (<>) "header" . headerArgName) hs
64+
65+ captures = map captureArg
66+ . filter isCapture
67+ $ req ^. reqUrl.path
68+
69+ hs = req ^. reqHeaders
70+
71+ queryparams = req ^.. reqUrl.queryStr.traverse
72+
73+ body = if req ^. reqBody
74+ then [requestBody opts]
75+ else []
76+
77+ dataBody =
78+ if req ^. reqBody
79+ then " , data: body\n" <>
80+ " , responseType: 'json'\n"
81+ else ""
82+
83+ withCreds =
84+ if withCredentials aopts
85+ then " , withCredentials: true\n"
86+ else ""
87+
88+ xsrfCookie =
89+ case xsrfCookieName aopts of
90+ Just name -> " , xsrfCookieName: '" <> name <> "'\n"
91+ Nothing -> ""
92+
93+ xsrfHeader =
94+ case xsrfHeaderName aopts of
95+ Just name -> " , xsrfHeaderName: '" <> name <> "'\n"
96+ Nothing -> ""
97+
98+ reqheaders =
99+ if null hs
100+ then ""
101+ else " , headers: { " <> headersStr <> " }\n"
102+
103+ where headersStr = intercalate ", " $ map headerStr hs
104+ headerStr header = "\"" ++
105+ headerArgName header ++
106+ "\": " ++ show header
107+
108+ namespace =
109+ if hasNoModule
110+ then "var "
111+ else (moduleName opts) <> "."
112+ where
113+ hasNoModule = null (moduleName opts)
114+
115+ fname = namespace <> (functionNameBuilder opts $ req ^. funcName)
116+
117+ method = map toLower $ req ^. reqMethod
118+ url = if url' == "'" then "'/'" else url'
119+ url' = "'"
120+ ++ urlPrefix opts
121+ ++ urlArgs
122+ ++ queryArgs
123+
124+ urlArgs = jsSegments
125+ $ req ^.. reqUrl.path.traverse
126+
127+ queryArgs = if null queryparams
128+ then ""
129+ else " + '?" ++ jsParams queryparams
servant-js/test/Servant/JSSpec.hs+35−7
@@ -17,6 +17,7 @@ import Servant.JS
1717import qualified Servant.JS.Vanilla as JS
1818import qualified Servant.JS.JQuery as JQ
1919import qualified Servant.JS.Angular as NG
20+import qualified Servant.JS.Axios as AX
2021import Servant.JSSpec.CustomHeaders
2122
2223type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool
@@ -55,13 +56,15 @@ data TestNames = Vanilla
5556 | JQueryCustom
5657 | Angular
5758 | AngularCustom
59+ | Axios
60+ | AxiosCustom
5861 deriving (Show, Eq)
5962
6063customOptions :: CommonGeneratorOptions
61−customOptions = defCommonGeneratorOptions {
62− successCallback = "okCallback",
63− errorCallback = "errorCallback"
64− }
64+customOptions = defCommonGeneratorOptions
65+ { successCallback = "okCallback"
66+ , errorCallback = "errorCallback"
67+ }
6568
6669spec :: Spec
6770spec = describe "Servant.JQuery" $ do
@@ -71,9 +74,35 @@ spec = describe "Servant.JQuery" $ do
7174 generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions)
7275 generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
7376 generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
77+ generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
78+ generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { withCredentials = True }) customOptions)
7479
7580 angularSpec Angular
76− angularSpec AngularCustom
81+ axiosSpec
82+ --angularSpec AngularCustom
83+
84+axiosSpec :: Spec
85+axiosSpec = describe specLabel $ do
86+ it "should add withCredentials when needed" $ do
87+ let jsText = genJS withCredOpts $ listFromAPI (Proxy :: Proxy TestAPI)
88+ output jsText
89+ jsText `shouldContain` ("withCredentials: true")
90+ it "should add xsrfCookieName when needed" $ do
91+ let jsText = genJS cookieOpts $ listFromAPI (Proxy :: Proxy TestAPI)
92+ output jsText
93+ jsText `shouldContain` ("xsrfCookieName: 'MyXSRFcookie'")
94+ it "should add withCredentials when needed" $ do
95+ let jsText = genJS headerOpts $ listFromAPI (Proxy :: Proxy TestAPI)
96+ output jsText
97+ jsText `shouldContain` ("xsrfHeaderName: 'MyXSRFheader'")
98+ where
99+ specLabel = "Axios"
100+ output _ = return ()
101+ withCredOpts = AX.defAxiosOptions { AX.withCredentials = True }
102+ cookieOpts = AX.defAxiosOptions { AX.xsrfCookieName = Just "MyXSRFcookie" }
103+ headerOpts = AX.defAxiosOptions { AX.xsrfHeaderName = Just "MyXSRFheader" }
104+ genJS :: AxiosOptions -> [AjaxReq] -> String
105+ genJS opts req = concat $ map (AX.generateAxiosJS opts) req
77106
78107angularSpec :: TestNames -> Spec
79108angularSpec test = describe specLabel $ do
@@ -92,8 +121,7 @@ angularSpec test = describe specLabel $ do
92121 output jsText
93122 jsText `shouldNotContain` "getsomething($http, "
94123 where
95− specLabel = "generateJS(" ++ (show test) ++ ")"
96− --output = putStrLn
124+ specLabel = "AngularJS(" ++ (show test) ++ ")"
97125 output _ = return ()
98126 testName = "MyService"
99127 ngOpts = NG.defAngularOptions { NG.serviceName = testName }