From 4362112f31ef5423e4683b0c8244ecdff9e49678 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Mon, 27 Jul 2015 16:29:08 +0200 Subject: [PATCH 01/10] Add preliminary Axios support --- servant-js/servant-js.cabal | 1 + servant-js/src/Servant/JS.hs | 5 ++ servant-js/src/Servant/JS/Axios.hs | 88 ++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 servant-js/src/Servant/JS/Axios.hs diff --git a/servant-js/servant-js.cabal b/servant-js/servant-js.cabal index 2fa38e0c..f80a6a60 100644 --- a/servant-js/servant-js.cabal +++ b/servant-js/servant-js.cabal @@ -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 diff --git a/servant-js/src/Servant/JS.hs b/servant-js/src/Servant/JS.hs index 1367fffd..2d82c77f 100644 --- a/servant-js/src/Servant/JS.hs +++ b/servant-js/src/Servant/JS.hs @@ -95,6 +95,10 @@ module Servant.JS , AngularOptions(..) , defAngularOptions + , -- * Axios code generation + axios + , axiosWith + , -- * Misc. listFromAPI , javascript @@ -106,6 +110,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 diff --git a/servant-js/src/Servant/JS/Axios.hs b/servant-js/src/Servant/JS/Axios.hs new file mode 100644 index 00000000..bd109198 --- /dev/null +++ b/servant-js/src/Servant/JS/Axios.hs @@ -0,0 +1,88 @@ +module Servant.JS.Axios where + +import Servant.JS.Internal +import Control.Lens +import Data.Char (toLower) +import Data.List +import Data.Monoid + +-- | Generate regular javacript functions that use +-- the axios library, using default values for 'CommonGeneratorOptions'. +axios :: JavaScriptGenerator +axios = axiosWith defCommonGeneratorOptions + +-- | Generate regular javascript functions that use the axios library. +axiosWith :: CommonGeneratorOptions -> JavaScriptGenerator +axiosWith opts = intercalate "\n\n" . map (generateAxiosJSWith opts) + +-- | js codegen using axios library using default options +generateAxiosJS :: AjaxReq -> String +generateAxiosJS = generateAxiosJSWith defCommonGeneratorOptions + +-- | js codegen using axios library +generateAxiosJSWith :: CommonGeneratorOptions -> AjaxReq -> String +generateAxiosJSWith opts req = "\n" <> + fname <> " = function(" <> argsStr <> ")\n" + <> "{\n" + <> " return axios." <> method <> "(" <> url <> ",\n" + <> dataBody + <> reqheaders + <> " });\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: JSON.stringify(body)\n" <> + " , responseType: 'json'\n" + else "" + + 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 <> (functionRenamer 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 From 2e2eac654354a250a4127eeb2907daeb1f26f652 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Mon, 27 Jul 2015 16:34:00 +0200 Subject: [PATCH 02/10] Let example support Axios --- servant-js/examples/counter.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/servant-js/examples/counter.hs b/servant-js/examples/counter.hs index 86f274dd..5462b6f2 100644 --- a/servant-js/examples/counter.hs +++ b/servant-js/examples/counter.hs @@ -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 From 3ff1f5c95359b9c4124db2377fbdea860c1762c1 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Mon, 27 Jul 2015 16:49:52 +0200 Subject: [PATCH 03/10] Update tests for Axios --- servant-js/src/Servant/JS/Axios.hs | 6 ++++-- servant-js/test/Servant/JSSpec.hs | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/servant-js/src/Servant/JS/Axios.hs b/servant-js/src/Servant/JS/Axios.hs index bd109198..1dac64f1 100644 --- a/servant-js/src/Servant/JS/Axios.hs +++ b/servant-js/src/Servant/JS/Axios.hs @@ -24,7 +24,9 @@ generateAxiosJSWith :: CommonGeneratorOptions -> AjaxReq -> String generateAxiosJSWith opts req = "\n" <> fname <> " = function(" <> argsStr <> ")\n" <> "{\n" - <> " return axios." <> method <> "(" <> url <> ",\n" + <> " return axios(" <> url <> ",\n" + <> " {\n" + <> " method: '" <> method <> "'\n" <> dataBody <> reqheaders <> " });\n" @@ -76,7 +78,7 @@ generateAxiosJSWith opts req = "\n" <> method = map toLower $ req ^. reqMethod url = if url' == "'" then "'/'" else url' url' = "'" - ++ urlPrefix opts + -- ++ urlPrefix opts ++ urlArgs ++ queryArgs diff --git a/servant-js/test/Servant/JSSpec.hs b/servant-js/test/Servant/JSSpec.hs index 5ff60f9a..772e05e2 100644 --- a/servant-js/test/Servant/JSSpec.hs +++ b/servant-js/test/Servant/JSSpec.hs @@ -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,6 +56,8 @@ data TestNames = Vanilla | JQueryCustom | Angular | AngularCustom + | Axios + | AxiosCustom deriving (Show, Eq) customOptions :: CommonGeneratorOptions @@ -71,6 +74,8 @@ 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 + generateJSSpec AxiosCustom (AX.generateAxiosJSWith customOptions) angularSpec Angular angularSpec AngularCustom @@ -143,7 +148,7 @@ generateJSSpec n gen = describe specLabel $ do parseFromString jsStr `shouldSatisfy` isRight where specLabel = "generateJS(" ++ (show n) ++ ")" - output _ = return () + output = putStrLn genJS req = gen req header :: TestNames -> String -> String -> String header v headerName headerValue From 17ca343b45a1c10ecf36eac99be40d70a5ca795e Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Tue, 28 Jul 2015 01:25:00 +0200 Subject: [PATCH 04/10] Update example with Axios version --- servant-js/examples/www/axios/axios.min.js | 10 ++++++ servant-js/examples/www/axios/index.html | 40 ++++++++++++++++++++++ servant-js/examples/www/index.html | 10 +++--- servant-js/src/Servant/JS/Axios.hs | 5 ++- 4 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 servant-js/examples/www/axios/axios.min.js create mode 100644 servant-js/examples/www/axios/index.html diff --git a/servant-js/examples/www/axios/axios.min.js b/servant-js/examples/www/axios/axios.min.js new file mode 100644 index 00000000..3e3e2da5 --- /dev/null +++ b/servant-js/examples/www/axios/axios.min.js @@ -0,0 +1,10 @@ +/* axios v0.5.4 | (c) 2015 by Matt Zabriskie */ +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){/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE + * @version 2.0.1 + */ +(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;s1)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;++n0&&(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}}]); +//# sourceMappingURL=axios.min.map \ No newline at end of file diff --git a/servant-js/examples/www/axios/index.html b/servant-js/examples/www/axios/index.html new file mode 100644 index 00000000..25820678 --- /dev/null +++ b/servant-js/examples/www/axios/index.html @@ -0,0 +1,40 @@ + + + Servant: counter + + + +

Axios version

+Counter: 0 + + + + + + + diff --git a/servant-js/examples/www/index.html b/servant-js/examples/www/index.html index 4c55aea4..b9411ac1 100644 --- a/servant-js/examples/www/index.html +++ b/servant-js/examples/www/index.html @@ -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%} - - - - + + + + + diff --git a/servant-js/src/Servant/JS/Axios.hs b/servant-js/src/Servant/JS/Axios.hs index 1dac64f1..db97cbcf 100644 --- a/servant-js/src/Servant/JS/Axios.hs +++ b/servant-js/src/Servant/JS/Axios.hs @@ -24,9 +24,8 @@ generateAxiosJSWith :: CommonGeneratorOptions -> AjaxReq -> String generateAxiosJSWith opts req = "\n" <> fname <> " = function(" <> argsStr <> ")\n" <> "{\n" - <> " return axios(" <> url <> ",\n" - <> " {\n" - <> " method: '" <> method <> "'\n" + <> " return axios({ url: " <> url <> "\n" + <> " , method: '" <> method <> "'\n" <> dataBody <> reqheaders <> " });\n" From b085e63aa78e7b0cdb57e6e42cee8db040c2edef Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Tue, 28 Jul 2015 01:27:20 +0200 Subject: [PATCH 05/10] Removing output from test --- servant-js/test/Servant/JSSpec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servant-js/test/Servant/JSSpec.hs b/servant-js/test/Servant/JSSpec.hs index 772e05e2..be338901 100644 --- a/servant-js/test/Servant/JSSpec.hs +++ b/servant-js/test/Servant/JSSpec.hs @@ -148,7 +148,7 @@ generateJSSpec n gen = describe specLabel $ do parseFromString jsStr `shouldSatisfy` isRight where specLabel = "generateJS(" ++ (show n) ++ ")" - output = putStrLn + output _ = return () genJS req = gen req header :: TestNames -> String -> String -> String header v headerName headerValue From 522dc3c2cbf40bbee46a6cbbfb7ff39a4f2380e9 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Tue, 28 Jul 2015 11:33:36 +0200 Subject: [PATCH 06/10] Axios fix the request body --- servant-js/src/Servant/JS/Axios.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servant-js/src/Servant/JS/Axios.hs b/servant-js/src/Servant/JS/Axios.hs index db97cbcf..5cac7253 100644 --- a/servant-js/src/Servant/JS/Axios.hs +++ b/servant-js/src/Servant/JS/Axios.hs @@ -51,7 +51,7 @@ generateAxiosJSWith opts req = "\n" <> dataBody = if req ^. reqBody - then " , data: JSON.stringify(body)\n" <> + then " , data: body\n" <> " , responseType: 'json'\n" else "" From 60d94be0e9ee4c159bebdd8dc027d12b3282cc17 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Tue, 28 Jul 2015 15:06:00 +0200 Subject: [PATCH 07/10] Add a configuration mapping to Axios --- servant-js/src/Servant/JS.hs | 2 ++ servant-js/src/Servant/JS/Axios.hs | 58 +++++++++++++++++++++++++----- servant-js/test/Servant/JSSpec.hs | 15 ++++---- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/servant-js/src/Servant/JS.hs b/servant-js/src/Servant/JS.hs index 2d82c77f..23508ea4 100644 --- a/servant-js/src/Servant/JS.hs +++ b/servant-js/src/Servant/JS.hs @@ -98,6 +98,8 @@ module Servant.JS , -- * Axios code generation axios , axiosWith + , AxiosOptions(..) + , defAxiosOptions , -- * Misc. listFromAPI diff --git a/servant-js/src/Servant/JS/Axios.hs b/servant-js/src/Servant/JS/Axios.hs index 5cac7253..149e06b2 100644 --- a/servant-js/src/Servant/JS/Axios.hs +++ b/servant-js/src/Servant/JS/Axios.hs @@ -6,28 +6,53 @@ 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 :: JavaScriptGenerator -axios = axiosWith defCommonGeneratorOptions +axios :: AxiosOptions -> JavaScriptGenerator +axios aopts = axiosWith aopts defCommonGeneratorOptions -- | Generate regular javascript functions that use the axios library. -axiosWith :: CommonGeneratorOptions -> JavaScriptGenerator -axiosWith opts = intercalate "\n\n" . map (generateAxiosJSWith opts) +axiosWith :: AxiosOptions -> CommonGeneratorOptions -> JavaScriptGenerator +axiosWith aopts opts = intercalate "\n\n" . map (generateAxiosJSWith aopts opts) -- | js codegen using axios library using default options -generateAxiosJS :: AjaxReq -> String -generateAxiosJS = generateAxiosJSWith defCommonGeneratorOptions +generateAxiosJS :: AxiosOptions -> AjaxReq -> String +generateAxiosJS aopts = generateAxiosJSWith aopts defCommonGeneratorOptions -- | js codegen using axios library -generateAxiosJSWith :: CommonGeneratorOptions -> AjaxReq -> String -generateAxiosJSWith opts req = "\n" <> +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" @@ -55,10 +80,25 @@ generateAxiosJSWith opts req = "\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" + else " , headers: { " <> headersStr <> " }\n" where headersStr = intercalate ", " $ map headerStr hs headerStr header = "\"" ++ diff --git a/servant-js/test/Servant/JSSpec.hs b/servant-js/test/Servant/JSSpec.hs index be338901..0b7fafab 100644 --- a/servant-js/test/Servant/JSSpec.hs +++ b/servant-js/test/Servant/JSSpec.hs @@ -61,10 +61,10 @@ data TestNames = Vanilla deriving (Show, Eq) customOptions :: CommonGeneratorOptions -customOptions = defCommonGeneratorOptions { - successCallback = "okCallback", - errorCallback = "errorCallback" - } +customOptions = defCommonGeneratorOptions + { successCallback = "okCallback" + , errorCallback = "errorCallback" + } spec :: Spec spec = describe "Servant.JQuery" $ do @@ -74,11 +74,11 @@ 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 - generateJSSpec AxiosCustom (AX.generateAxiosJSWith customOptions) + generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions) + generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { withCredentials = True }) customOptions) angularSpec Angular - angularSpec AngularCustom + --angularSpec AngularCustom angularSpec :: TestNames -> Spec angularSpec test = describe specLabel $ do @@ -98,7 +98,6 @@ angularSpec test = describe specLabel $ do jsText `shouldNotContain` "getsomething($http, " where specLabel = "generateJS(" ++ (show test) ++ ")" - --output = putStrLn output _ = return () testName = "MyService" ngOpts = NG.defAngularOptions { NG.serviceName = testName } From 915ed652e1fde06d342d4b3e105ca8e511300c22 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Tue, 28 Jul 2015 15:44:55 +0200 Subject: [PATCH 08/10] Upgrading tests for Axios --- servant-js/test/Servant/JSSpec.hs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/servant-js/test/Servant/JSSpec.hs b/servant-js/test/Servant/JSSpec.hs index 0b7fafab..ec7bc000 100644 --- a/servant-js/test/Servant/JSSpec.hs +++ b/servant-js/test/Servant/JSSpec.hs @@ -78,8 +78,32 @@ spec = describe "Servant.JQuery" $ do generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { withCredentials = True }) customOptions) angularSpec Angular + 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 it "should implement a service globally" $ do @@ -97,7 +121,7 @@ angularSpec test = describe specLabel $ do output jsText jsText `shouldNotContain` "getsomething($http, " where - specLabel = "generateJS(" ++ (show test) ++ ")" + specLabel = "AngularJS(" ++ (show test) ++ ")" output _ = return () testName = "MyService" ngOpts = NG.defAngularOptions { NG.serviceName = testName } From 2c0fe980eb4b2d999b4208b072946ba986948852 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Tue, 28 Jul 2015 16:46:00 +0200 Subject: [PATCH 09/10] Rename also function name transformer for Axios --- servant-js/examples/www/axios/index.html | 6 +++--- servant-js/src/Servant/JS/Axios.hs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/servant-js/examples/www/axios/index.html b/servant-js/examples/www/axios/index.html index 25820678..2f3a0d7c 100644 --- a/servant-js/examples/www/axios/index.html +++ b/servant-js/examples/www/axios/index.html @@ -17,11 +17,11 @@ diff --git a/servant-js/src/Servant/JS/Axios.hs b/servant-js/src/Servant/JS/Axios.hs index 149e06b2..c955dd51 100644 --- a/servant-js/src/Servant/JS/Axios.hs +++ b/servant-js/src/Servant/JS/Axios.hs @@ -112,7 +112,7 @@ generateAxiosJSWith aopts opts req = "\n" <> where hasNoModule = null (moduleName opts) - fname = namespace <> (functionRenamer opts $ req ^. funcName) + fname = namespace <> (functionNameBuilder opts $ req ^. funcName) method = map toLower $ req ^. reqMethod url = if url' == "'" then "'/'" else url' From 4cab814e172ae108b268419044ccac710111fc72 Mon Sep 17 00:00:00 2001 From: Freezeboy Date: Tue, 28 Jul 2015 16:47:19 +0200 Subject: [PATCH 10/10] Add urlPrefix support for Axios --- servant-js/src/Servant/JS/Axios.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servant-js/src/Servant/JS/Axios.hs b/servant-js/src/Servant/JS/Axios.hs index c955dd51..bd62f80f 100644 --- a/servant-js/src/Servant/JS/Axios.hs +++ b/servant-js/src/Servant/JS/Axios.hs @@ -117,7 +117,7 @@ generateAxiosJSWith aopts opts req = "\n" <> method = map toLower $ req ^. reqMethod url = if url' == "'" then "'/'" else url' url' = "'" - -- ++ urlPrefix opts + ++ urlPrefix opts ++ urlArgs ++ queryArgs