on master help sign in
Merge pull request #173 from freezeboy/axios
3dc304bAlp Mestanogullari
servant-js/examples/counter.hs+2
@@ -92,6 +92,8 @@ main = do
92
92
93 writeJSForAPI testApi (angular defAngularOptions) (www </> "angular" </> "api.js")
93 writeJSForAPI testApi (angular defAngularOptions) (www </> "angular" </> "api.js")
94
94
95+ writeJSForAPI testApi axios (www </> "axios" </> "api.js")
96+
95 writeServiceJS (www </> "angular" </> "api.service.js")
97 writeServiceJS (www </> "angular" </> "api.service.js")
96
98
97 -- setup a shared counter
99 -- 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 @@
5 body { text-align: center; }
5 body { text-align: center; }
6 #counter { color: green; }
6 #counter { color: green; }
7 #inc { margin: 0px 20px; background-color: green; color: white; }
7 #inc { margin: 0px 20px; background-color: green; color: white; }
8+ iframe { height: 20%; width: 80%}
8 </style>
9 </style>
9</head>
10</head>
10<body>
11<body>
11
12
12−<iframe src="vanilla/" width="80%" height="25%"></iframe>
13+<iframe src="vanilla/"></iframe>
13−<iframe src="jquery/" width="80%" height="25%"></iframe>
14+<iframe src="jquery/"></iframe>
14−<iframe src="angular/" width="80%" height="25%"></iframe>
15+<iframe src="angular/"></iframe>
15−<iframe src="angular/service.html" width="80%" height="25%"></iframe>
16+<iframe src="angular/service.html"></iframe>
17+<iframe src="axios/"></iframe>
16</body>
18</body>
17</html>
19</html>
servant-js/servant-js.cabal+1
@@ -36,6 +36,7 @@ flag example
36library
36library
37 exposed-modules: Servant.JS
37 exposed-modules: Servant.JS
38 Servant.JS.Angular
38 Servant.JS.Angular
39+ Servant.JS.Axios
39 Servant.JS.JQuery
40 Servant.JS.JQuery
40 Servant.JS.Vanilla
41 Servant.JS.Vanilla
41 Servant.JS.Internal
42 Servant.JS.Internal
servant-js/src/Servant/JS.hs+7
@@ -100,6 +100,12 @@ module Servant.JS
100 , AngularOptions(..)
100 , AngularOptions(..)
101 , defAngularOptions
101 , defAngularOptions
102
102
103+ , -- * Axios code generation
104+ axios
105+ , axiosWith
106+ , AxiosOptions(..)
107+ , defAxiosOptions
108+
103 , -- * Misc.
109 , -- * Misc.
104 listFromAPI
110 listFromAPI
105 , javascript
111 , javascript
@@ -111,6 +117,7 @@ module Servant.JS
111import Data.Proxy
117import Data.Proxy
112import Servant.API
118import Servant.API
113import Servant.JS.Angular
119import Servant.JS.Angular
120+import Servant.JS.Axios
114import Servant.JS.Internal
121import Servant.JS.Internal
115import Servant.JS.JQuery
122import Servant.JS.JQuery
116import Servant.JS.Vanilla
123import 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
17import qualified Servant.JS.Vanilla as JS
17import qualified Servant.JS.Vanilla as JS
18import qualified Servant.JS.JQuery as JQ
18import qualified Servant.JS.JQuery as JQ
19import qualified Servant.JS.Angular as NG
19import qualified Servant.JS.Angular as NG
20+import qualified Servant.JS.Axios as AX
20import Servant.JSSpec.CustomHeaders
21import Servant.JSSpec.CustomHeaders
21
22
22type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool
23type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool
@@ -55,13 +56,15 @@ data TestNames = Vanilla
55 | JQueryCustom
56 | JQueryCustom
56 | Angular
57 | Angular
57 | AngularCustom
58 | AngularCustom
59+ | Axios
60+ | AxiosCustom
58 deriving (Show, Eq)
61 deriving (Show, Eq)
59
62
60customOptions :: CommonGeneratorOptions
63customOptions :: CommonGeneratorOptions
61−customOptions = defCommonGeneratorOptions {
64+customOptions = defCommonGeneratorOptions
62− successCallback = "okCallback",
65+ { successCallback = "okCallback"
63− errorCallback = "errorCallback"
66+ , errorCallback = "errorCallback"
64− }
67+ }
65
68
66spec :: Spec
69spec :: Spec
67spec = describe "Servant.JQuery" $ do
70spec = describe "Servant.JQuery" $ do
@@ -71,9 +74,35 @@ spec = describe "Servant.JQuery" $ do
71 generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions)
74 generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions)
72 generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
75 generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
73 generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
76 generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
77+ generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
78+ generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { withCredentials = True }) customOptions)
74
79
75 angularSpec Angular
80 angularSpec Angular
81+ axiosSpec
76− angularSpec AngularCustom
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
77
106
78angularSpec :: TestNames -> Spec
107angularSpec :: TestNames -> Spec
79angularSpec test = describe specLabel $ do
108angularSpec test = describe specLabel $ do
@@ -92,8 +121,7 @@ angularSpec test = describe specLabel $ do
92 output jsText
121 output jsText
93 jsText `shouldNotContain` "getsomething($http, "
122 jsText `shouldNotContain` "getsomething($http, "
94 where
123 where
95− specLabel = "generateJS(" ++ (show test) ++ ")"
124+ specLabel = "AngularJS(" ++ (show test) ++ ")"
96− --output = putStrLn
97 output _ = return ()
125 output _ = return ()
98 testName = "MyService"
126 testName = "MyService"
99 ngOpts = NG.defAngularOptions { NG.serviceName = testName }
127 ngOpts = NG.defAngularOptions { NG.serviceName = testName }