├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── UrlBuilder.js ├── bower.json ├── browser.js ├── client.js ├── client ├── default.js ├── jsonp.js ├── node.js └── xhr.js ├── docs ├── README.md ├── clients.md ├── interceptors.md ├── interfaces.md ├── mime.md └── wire.md ├── interceptor.js ├── interceptor ├── basicAuth.js ├── csrf.js ├── defaultRequest.js ├── entity.js ├── errorCode.js ├── hateoas.js ├── jsonp.js ├── location.js ├── mime.js ├── oAuth.js ├── params.js ├── pathPrefix.js ├── retry.js ├── template.js └── timeout.js ├── mime.js ├── mime ├── registry.js └── type │ ├── application │ ├── hal.js │ ├── json.js │ └── x-www-form-urlencoded.js │ ├── multipart │ └── form-data.js │ └── text │ └── plain.js ├── node.js ├── package.json ├── parsers ├── rfc5988.js └── rfc5988.pegjs ├── rest.js ├── test ├── UrlBuilder-test.js ├── browser-test-browser.js ├── browsers │ ├── android-4.3.json │ ├── android-4.4.json │ ├── android-5.1.json │ ├── chrome.json │ ├── edge.json │ ├── firefox-38.json │ ├── firefox-45.json │ ├── firefox.json │ ├── ie-11.json │ ├── ios-8.4.json │ ├── ios-9.2.json │ ├── safari-8.json │ └── safari-9.json ├── buster.js ├── client-test.js ├── client │ ├── fixtures │ │ ├── data.js │ │ ├── noop.js │ │ └── throw.js │ ├── jsonp-test-browser.js │ ├── node-ssl.crt │ ├── node-ssl.key │ ├── node-test-node.js │ └── xhr-test-browser.js ├── curl-config.js ├── failOnThrow.js ├── interceptor-test.js ├── interceptor │ ├── basicAuth-test.js │ ├── csrf-test.js │ ├── defaultRequest-test.js │ ├── entity-test.js │ ├── errorCode-test.js │ ├── hateoas-test.js │ ├── jsonp-test.js │ ├── location-test.js │ ├── mime-test.js │ ├── oAuth-test-browser.js │ ├── params-test.js │ ├── pathPrefix-test.js │ ├── retry-test.js │ ├── template-test.js │ └── timeout-test.js ├── mime-test.js ├── mime │ ├── registry-test.js │ └── type │ │ ├── application │ │ ├── hal-test.js │ │ ├── json-test.js │ │ └── x-www-form-urlencoded-test.js │ │ ├── multipart │ │ └── form-data-test-browser.js │ │ └── text │ │ └── plain-test.js ├── node-test-node.js ├── rest-test.js ├── run.js ├── util │ ├── attempt-test.js │ ├── base64-test.js │ ├── delay-test.js │ ├── find-test.js │ ├── lazyPromise-test.js │ ├── mixin-test.js │ ├── normalizeHeaderName-test.js │ ├── pubsub-test.js │ ├── responsePromise-test.js │ ├── uriEncoder-test.js │ └── uriTemplate-test.js ├── version-test-node.js └── wire-test.js ├── util ├── Promise.js ├── attempt.js ├── base64.js ├── delay.js ├── find.js ├── lazyPromise.js ├── mixin.js ├── normalizeHeaderName.js ├── pubsub.js ├── responsePromise.js ├── uriEncoder.js └── uriTemplate.js └── wire.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .buildpath 3 | .classpath 4 | .idea 5 | .project 6 | .settings 7 | /node_modules 8 | /npm-debug.log 9 | /sauce_connect.log* 10 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | parsers 3 | test/**/fixtures -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | // Custom globals 4 | "predef" : [ 5 | "define" 6 | ], 7 | 8 | 9 | // Environemnts 10 | "browser" : true, // defines globals exposed by modern browsers: 11 | "couch" : false, // defines globals exposed by CouchDB 12 | "node" : true, // defines globals available when your code is running inside of Node.js 13 | "rhino" : false, // defines globals available when your code is running inside of Rhino 14 | "worker" : true, // defines globals available when your code is running inside of a Web Worker 15 | "wsh" : false, // defines globals available when your code is running as a script for the Windows Script Host. 16 | 17 | "devel" : false, // defines globals that are usually used for logging poor-man's debugging: console, alert 18 | "es5" : false, // use ECMAScript 5 specific features such as getters and setters 19 | "esnext" : false, // use ES.next specific features such as const 20 | "nonstandard" : false, // defines non-standard but widely adopted globals such as escape and unescape 21 | 22 | 23 | // Whitespace 24 | "indent" : 4, // enforces specific tab width 25 | "smarttabs" : true, // suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only 26 | "trailing" : true, // error to leave a trailing whitespace in your code 27 | 28 | 29 | // Code Complexity 30 | "maxparams" : 5, // max number of formal parameters allowed per function 31 | "maxdepth" : 3, // control how nested do you want your blocks to be 32 | "maxstatements" : 40, // max number of statements allowed per function 33 | "maxcomplexity" : 8, // control cyclomatic complexity throughout your code 34 | "maxlen" : 0, // maximum length of a line 35 | 36 | 37 | // Enforcing Options 38 | "bitwise" : true, // prohibits the use of bitwise operators 39 | "camelcase" : true, // force all variable names to use either camelCase style or UPPER_CASE with underscores 40 | "curly" : true, // always put curly braces around blocks in loops and conditionals 41 | "eqeqeq" : true, // prohibits the use of == and != in favor of === and !== 42 | "forin" : true, // requires all for in loops to filter object's items 43 | "immed" : true, // prohibits the use of immediate function invocations without wrapping them in parentheses 44 | "latedef" : true, // prohibits the use of a variable before it was defined 45 | "newcap" : true, // requires you to capitalize names of constructor functions 46 | "noarg" : true, // prohibits the use of arguments.caller and arguments.callee 47 | "noempty" : true, // warns when you have an empty block in your code 48 | "nonew" : true, // prohibits the use of constructor functions for side-effects 49 | "plusplus" : true, // prohibits the use of unary increment and decrement operators 50 | "quotmark" : "single", // enforces the consistency of quotation marks used throughout your code 51 | "regexp" : true, // prohibits the use of unsafe . in regular expressions 52 | "undef" : true, // prohibits the use of explicitly undeclared variables 53 | "unused" : true, // warns when you define and never use your variables 54 | "strict" : false, // requires all functions to run in EcmaScript 5's strict mode 55 | 56 | 57 | // Relaxing Options 58 | "asi" : false, // suppresses warnings about missing semicolons 59 | "boss" : false, // suppresses warnings about the use of assignments in cases where comparisons are expected 60 | "debug" : false, // suppresses warnings about the debugger statements 61 | "eqnull" : false, // suppresses warnings about == null comparisons 62 | "evil" : false, // suppresses warnings about the use of eval 63 | "expr" : false, // suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls 64 | "funcscope" : false, // suppresses warnings about declaring variables inside of control structures while accessing them later from the outside 65 | "globalstrict" : false, // suppresses warnings about the use of global strict mode 66 | "iterator" : false, // suppresses warnings about the __iterator__ property 67 | "lastsemic" : false, // suppresses warnings about missing semicolon 68 | "laxbreak" : false, // suppresses most of the warnings about possibly unsafe line breaking 69 | "laxcomma" : false, // suppresses warnings about comma-first coding style 70 | "loopfunc" : false, // suppresses warnings about functions inside of loops 71 | "multistr" : false, // suppresses warnings about multi-line strings 72 | "onecase" : false, // suppresses warnings about switches with just one case 73 | "proto" : false, // suppresses warnings about the __proto__ property 74 | "regexdash" : false, // suppresses warnings about unescaped - in the end of regular expressions 75 | "scripturl" : false, // suppresses warnings about the use of script-targeted URLs—such as javascript:... 76 | "shadow" : false, // suppresses warnings about variable shadowing 77 | "sub" : false, // suppresses warnings about using [] notation when it can be expressed in dot notation: person['name'] vs. person.name 78 | "supernew" : false, // suppresses warnings about "weird" constructions like new function () { ... } and new Object; 79 | "validthis" : false // suppresses warnings about possible strict violations when the code is running in strict mode and you use this in a non-constructor function 80 | 81 | } 82 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | before_install: npm config set ca '' 4 | env: 5 | global: 6 | - BUSTER_TEST_OPT='--reporter specification' 7 | - SAUCE_USERNAME=cujojs-rest 8 | - SAUCE_ACCESS_KEY='083602fa-1a26-4c55-af2a-9d64a27f546e' 9 | - PORT=8000 10 | cache: 11 | directories: 12 | - node_modules 13 | sudo: false 14 | matrix: 15 | include: 16 | - node_js: '0.10' 17 | script: 'travis_retry npm run-script lint' 18 | - node_js: '0.10' 19 | script: 'travis_retry npm run-script buster' 20 | - node_js: '4' 21 | script: 'travis_retry npm run-script buster' 22 | - node_js: '6' 23 | script: 'travis_retry npm run-script buster' 24 | - node_js: '0.10' 25 | script: 'travis_retry npm run-script sauceme' 26 | env: 27 | - BROWSERS=test/browsers/chrome.json 28 | - node_js: '0.10' 29 | script: 'travis_retry npm run-script sauceme' 30 | env: 31 | - BROWSERS=test/browsers/firefox.json 32 | - node_js: '0.10' 33 | script: 'travis_retry npm run-script sauceme' 34 | env: 35 | - BROWSERS=test/browsers/firefox-45.json 36 | - node_js: '0.10' 37 | script: 'travis_retry npm run-script sauceme' 38 | env: 39 | - BROWSERS=test/browsers/firefox-38.json 40 | - node_js: '0.10' 41 | script: 'travis_retry npm run-script sauceme' 42 | env: 43 | - BROWSERS=test/browsers/edge.json 44 | - node_js: '0.10' 45 | script: 'travis_retry npm run-script sauceme' 46 | env: 47 | - BROWSERS=test/browsers/edge.json 48 | - node_js: '0.10' 49 | script: 'npm run-script sauceme' 50 | env: 51 | - BROWSERS=test/browsers/ie-11.json 52 | - node_js: '0.10' 53 | script: 'travis_retry npm run-script sauceme' 54 | env: 55 | - BROWSERS=test/browsers/safari-9.json 56 | - node_js: '0.10' 57 | script: 'travis_retry npm run-script sauceme' 58 | env: 59 | - BROWSERS=test/browsers/safari-9.json 60 | - node_js: '0.10' 61 | script: 'npm run-script sauceme' 62 | env: 63 | - BROWSERS=test/browsers/safari-8.json 64 | - node_js: '0.10' 65 | script: 'travis_retry npm run-script sauceme' 66 | env: 67 | - BROWSERS=test/browsers/ios-9.2.json 68 | - node_js: '0.10' 69 | script: 'travis_retry npm run-script sauceme' 70 | env: 71 | - BROWSERS=test/browsers/ios-9.2.json 72 | - node_js: '0.10' 73 | script: 'travis_retry npm run-script sauceme' 74 | env: 75 | - BROWSERS=test/browsers/ios-8.4.json 76 | - node_js: '0.10' 77 | script: 'travis_retry npm run-script sauceme' 78 | env: 79 | - BROWSERS=test/browsers/android-5.1.json 80 | - node_js: '0.10' 81 | script: 'travis_retry npm run-script sauceme' 82 | env: 83 | - BROWSERS=test/browsers/android-4.4.json 84 | - node_js: '0.10' 85 | script: 'travis_retry npm run-script sauceme' 86 | env: 87 | - BROWSERS=test/browsers/android-4.3.json 88 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2015 the original author or authors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction,
including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished
to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | 22 | --- 23 | 24 | Code published by Scott Andrews or Jeremy Grelle prior to 2013-09-21 is 25 | copyright Pivotal and licensed under the above terms. 26 | 27 | GoPivotal, Inc. 28 | 1900 South Norfolk Street, Suite 125 29 | San Mateo, CA 94403 30 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest", 3 | "version": "2.0.0", 4 | "main": "./browser.js", 5 | "moduleType": ["node"], 6 | "dependencies": {}, 7 | "ignore": [ 8 | "docs", 9 | "test" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var rest = require('./client/default'), 11 | browser = require('./client/xhr'); 12 | 13 | rest.setPlatformDefaultClient(browser); 14 | 15 | module.exports = rest; 16 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Add common helper methods to a client impl 12 | * 13 | * @param {function} impl the client implementation 14 | * @param {Client} [target] target of this client, used when wrapping other clients 15 | * @returns {Client} the client impl with additional methods 16 | */ 17 | module.exports = function client(impl, target) { 18 | 19 | if (target) { 20 | 21 | /** 22 | * @returns {Client} the target client 23 | */ 24 | impl.skip = function skip() { 25 | return target; 26 | }; 27 | 28 | } 29 | 30 | /** 31 | * Allow a client to easily be wrapped by an interceptor 32 | * 33 | * @param {Interceptor} interceptor the interceptor to wrap this client with 34 | * @param [config] configuration for the interceptor 35 | * @returns {Client} the newly wrapped client 36 | */ 37 | impl.wrap = function wrap(interceptor, config) { 38 | return interceptor(impl, config); 39 | }; 40 | 41 | /** 42 | * @deprecated 43 | */ 44 | impl.chain = function chain() { 45 | if (typeof console !== 'undefined') { 46 | console.log('rest.js: client.chain() is deprecated, use client.wrap() instead'); 47 | } 48 | 49 | return impl.wrap.apply(this, arguments); 50 | }; 51 | 52 | return impl; 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /client/default.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Plain JS Object containing properties that represent an HTTP request. 12 | * 13 | * Depending on the capabilities of the underlying client, a request 14 | * may be cancelable. If a request may be canceled, the client will add 15 | * a canceled flag and cancel function to the request object. Canceling 16 | * the request will put the response into an error state. 17 | * 18 | * @field {string} [method='GET'] HTTP method, commonly GET, POST, PUT, DELETE or HEAD 19 | * @field {string|UrlBuilder} [path=''] path template with optional path variables 20 | * @field {Object} [params] parameters for the path template and query string 21 | * @field {Object} [headers] custom HTTP headers to send, in addition to the clients default headers 22 | * @field [entity] the HTTP entity, common for POST or PUT requests 23 | * @field {boolean} [canceled] true if the request has been canceled, set by the client 24 | * @field {Function} [cancel] cancels the request if invoked, provided by the client 25 | * @field {Client} [originator] the client that first handled this request, provided by the interceptor 26 | * 27 | * @class Request 28 | */ 29 | 30 | /** 31 | * Plain JS Object containing properties that represent an HTTP response 32 | * 33 | * @field {Object} [request] the request object as received by the root client 34 | * @field {Object} [raw] the underlying request object, like XmlHttpRequest in a browser 35 | * @field {number} [status.code] status code of the response (i.e. 200, 404) 36 | * @field {string} [status.text] status phrase of the response 37 | * @field {Object] [headers] response headers hash of normalized name, value pairs 38 | * @field [entity] the response body 39 | * 40 | * @class Response 41 | */ 42 | 43 | /** 44 | * HTTP client particularly suited for RESTful operations. 45 | * 46 | * @field {function} wrap wraps this client with a new interceptor returning the wrapped client 47 | * 48 | * @param {Request} the HTTP request 49 | * @returns {ResponsePromise} a promise the resolves to the HTTP response 50 | * 51 | * @class Client 52 | */ 53 | 54 | /** 55 | * Extended when.js Promises/A+ promise with HTTP specific helpers 56 | * 57 | * @method entity promise for the HTTP entity 58 | * @method status promise for the HTTP status code 59 | * @method headers promise for the HTTP response headers 60 | * @method header promise for a specific HTTP response header 61 | * 62 | * @class ResponsePromise 63 | * @extends Promise 64 | */ 65 | 66 | var client, target, platformDefault; 67 | 68 | client = require('../client'); 69 | 70 | if (typeof Promise !== 'function' && console && console.log) { 71 | console.log('An ES6 Promise implementation is required to use rest.js. See https://github.com/cujojs/when/blob/master/docs/es6-promise-shim.md for using when.js as a Promise polyfill.'); 72 | } 73 | 74 | /** 75 | * Make a request with the default client 76 | * @param {Request} the HTTP request 77 | * @returns {Promise} a promise the resolves to the HTTP response 78 | */ 79 | function defaultClient() { 80 | return target.apply(void 0, arguments); 81 | } 82 | 83 | /** 84 | * Change the default client 85 | * @param {Client} client the new default client 86 | */ 87 | defaultClient.setDefaultClient = function setDefaultClient(client) { 88 | target = client; 89 | }; 90 | 91 | /** 92 | * Obtain a direct reference to the current default client 93 | * @returns {Client} the default client 94 | */ 95 | defaultClient.getDefaultClient = function getDefaultClient() { 96 | return target; 97 | }; 98 | 99 | /** 100 | * Reset the default client to the platform default 101 | */ 102 | defaultClient.resetDefaultClient = function resetDefaultClient() { 103 | target = platformDefault; 104 | }; 105 | 106 | /** 107 | * @private 108 | */ 109 | defaultClient.setPlatformDefaultClient = function setPlatformDefaultClient(client) { 110 | if (platformDefault) { 111 | throw new Error('Unable to redefine platformDefaultClient'); 112 | } 113 | target = platformDefault = client; 114 | }; 115 | 116 | module.exports = client(defaultClient); 117 | -------------------------------------------------------------------------------- /client/jsonp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var UrlBuilder, responsePromise, client; 11 | 12 | UrlBuilder = require('../UrlBuilder'); 13 | responsePromise = require('../util/responsePromise'); 14 | client = require('../client'); 15 | 16 | // consider abstracting this into a util module 17 | function clearProperty(scope, propertyName) { 18 | try { 19 | delete scope[propertyName]; 20 | } 21 | catch (e) { 22 | // IE doesn't like to delete properties on the window object 23 | if (propertyName in scope) { 24 | scope[propertyName] = void 0; 25 | } 26 | } 27 | } 28 | 29 | function cleanupScriptNode(response) { 30 | try { 31 | if (response.raw && response.raw.parentNode) { 32 | response.raw.parentNode.removeChild(response.raw); 33 | } 34 | } catch (e) { 35 | // ignore 36 | } 37 | } 38 | 39 | function registerCallback(prefix, resolve, response, name) { 40 | if (!name) { 41 | do { 42 | name = prefix + Math.floor(new Date().getTime() * Math.random()); 43 | } 44 | while (name in window); 45 | } 46 | 47 | window[name] = function jsonpCallback(data) { 48 | response.entity = data; 49 | clearProperty(window, name); 50 | cleanupScriptNode(response); 51 | if (!response.request.canceled) { 52 | resolve(response); 53 | } 54 | }; 55 | 56 | return name; 57 | } 58 | 59 | /** 60 | * Executes the request as JSONP. 61 | * 62 | * @param {string} request.path the URL to load 63 | * @param {Object} [request.params] parameters to bind to the path 64 | * @param {string} [request.callback.param='callback'] the parameter name for 65 | * which the callback function name is the value 66 | * @param {string} [request.callback.prefix='jsonp'] prefix for the callback 67 | * function, as the callback is attached to the window object, a unique, 68 | * unobtrusive prefix is desired 69 | * @param {string} [request.callback.name=] pins the name of the 70 | * callback function, useful for cases where the server doesn't allow 71 | * custom callback names. Generally not recommended. 72 | * 73 | * @returns {Promise} 74 | */ 75 | module.exports = client(function jsonp(request) { 76 | return responsePromise.promise(function (resolve, reject) { 77 | 78 | var callbackName, callbackParams, script, firstScript, response; 79 | 80 | request = typeof request === 'string' ? { path: request } : request || {}; 81 | response = { request: request }; 82 | 83 | if (request.canceled) { 84 | response.error = 'precanceled'; 85 | reject(response); 86 | return; 87 | } 88 | 89 | request.callback = request.callback || {}; 90 | callbackName = registerCallback(request.callback.prefix || 'jsonp', resolve, response, request.callback.name); 91 | callbackParams = {}; 92 | callbackParams[request.callback.param || 'callback'] = callbackName; 93 | 94 | request.canceled = false; 95 | request.cancel = function cancel() { 96 | request.canceled = true; 97 | cleanupScriptNode(response); 98 | reject(response); 99 | }; 100 | 101 | script = document.createElement('script'); 102 | script.type = 'text/javascript'; 103 | script.async = true; 104 | script.src = response.url = new UrlBuilder(request.path, callbackParams).build(); 105 | 106 | function handlePossibleError() { 107 | if (typeof window[callbackName] === 'function') { 108 | response.error = 'loaderror'; 109 | clearProperty(window, callbackName); 110 | cleanupScriptNode(response); 111 | reject(response); 112 | } 113 | } 114 | script.onerror = function () { 115 | handlePossibleError(); 116 | }; 117 | script.onload = script.onreadystatechange = function (e) { 118 | // script tag load callbacks are completely non-standard 119 | // handle case where onreadystatechange is fired for an error instead of onerror 120 | if ((e && (e.type === 'load' || e.type === 'error')) || script.readyState === 'loaded') { 121 | handlePossibleError(); 122 | } 123 | }; 124 | 125 | response.raw = script; 126 | firstScript = document.getElementsByTagName('script')[0]; 127 | firstScript.parentNode.insertBefore(script, firstScript); 128 | 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /client/node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Jeremy Grelle 6 | * @author Scott Andrews 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var parser, http, https, mixin, normalizeHeaderName, responsePromise, client, httpsExp; 12 | 13 | parser = require('url'); 14 | http = require('http'); 15 | https = require('https'); 16 | mixin = require('../util/mixin'); 17 | normalizeHeaderName = require('../util/normalizeHeaderName'); 18 | responsePromise = require('../util/responsePromise'); 19 | client = require('../client'); 20 | 21 | httpsExp = /^https/i; 22 | 23 | // TODO remove once Node 0.6 is no longer supported 24 | Buffer.concat = Buffer.concat || function (list, length) { 25 | /*jshint plusplus:false, shadow:true */ 26 | // from https://github.com/joyent/node/blob/v0.8.21/lib/buffer.js 27 | if (!Array.isArray(list)) { 28 | throw new Error('Usage: Buffer.concat(list, [length])'); 29 | } 30 | 31 | if (list.length === 0) { 32 | return new Buffer(0); 33 | } else if (list.length === 1) { 34 | return list[0]; 35 | } 36 | 37 | if (typeof length !== 'number') { 38 | length = 0; 39 | for (var i = 0; i < list.length; i++) { 40 | var buf = list[i]; 41 | length += buf.length; 42 | } 43 | } 44 | 45 | var buffer = new Buffer(length); 46 | var pos = 0; 47 | for (var i = 0; i < list.length; i++) { 48 | var buf = list[i]; 49 | buf.copy(buffer, pos); 50 | pos += buf.length; 51 | } 52 | return buffer; 53 | }; 54 | 55 | module.exports = client(function node(request) { 56 | /*jshint maxcomplexity:20 */ 57 | return responsePromise.promise(function (resolve, reject) { 58 | 59 | var options, clientRequest, client, url, headers, entity, response; 60 | 61 | request = typeof request === 'string' ? { path: request } : request || {}; 62 | response = { request: request }; 63 | 64 | if (request.canceled) { 65 | response.error = 'precanceled'; 66 | reject(response); 67 | return; 68 | } 69 | 70 | url = response.url = request.path || ''; 71 | client = url.match(httpsExp) ? https : http; 72 | 73 | options = mixin({}, request.mixin, parser.parse(url)); 74 | 75 | entity = request.entity; 76 | request.method = request.method || (entity ? 'POST' : 'GET'); 77 | options.method = request.method; 78 | headers = options.headers = {}; 79 | Object.keys(request.headers || {}).forEach(function (name) { 80 | headers[normalizeHeaderName(name)] = request.headers[name]; 81 | }); 82 | if (!headers['Content-Length']) { 83 | headers['Content-Length'] = entity ? Buffer.byteLength(entity, 'utf8') : 0; 84 | } 85 | 86 | request.canceled = false; 87 | request.cancel = function cancel() { 88 | request.canceled = true; 89 | clientRequest.abort(); 90 | }; 91 | 92 | clientRequest = client.request(options, function (clientResponse) { 93 | // Array of Buffers to collect response chunks 94 | var buffers = []; 95 | 96 | response.raw = { 97 | request: clientRequest, 98 | response: clientResponse 99 | }; 100 | response.status = { 101 | code: clientResponse.statusCode 102 | // node doesn't provide access to the status text 103 | }; 104 | response.headers = {}; 105 | Object.keys(clientResponse.headers).forEach(function (name) { 106 | response.headers[normalizeHeaderName(name)] = clientResponse.headers[name]; 107 | }); 108 | 109 | clientResponse.on('data', function (data) { 110 | // Collect the next Buffer chunk 111 | buffers.push(data); 112 | }); 113 | 114 | clientResponse.on('end', function () { 115 | // Create the final response entity 116 | response.entity = buffers.length > 0 ? Buffer.concat(buffers).toString() : ''; 117 | buffers = null; 118 | 119 | resolve(response); 120 | }); 121 | }); 122 | 123 | clientRequest.on('error', function (e) { 124 | response.error = e; 125 | reject(response); 126 | }); 127 | 128 | if (entity) { 129 | clientRequest.write(entity); 130 | } 131 | clientRequest.end(); 132 | 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /client/xhr.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var normalizeHeaderName, responsePromise, client, headerSplitRE; 11 | 12 | normalizeHeaderName = require('../util/normalizeHeaderName'); 13 | responsePromise = require('../util/responsePromise'); 14 | client = require('../client'); 15 | 16 | // according to the spec, the line break is '\r\n', but doesn't hold true in practice 17 | headerSplitRE = /[\r|\n]+/; 18 | 19 | function parseHeaders(raw) { 20 | // Note: Set-Cookie will be removed by the browser 21 | var headers = {}; 22 | 23 | if (!raw) { return headers; } 24 | 25 | raw.trim().split(headerSplitRE).forEach(function (header) { 26 | var boundary, name, value; 27 | boundary = header.indexOf(':'); 28 | name = normalizeHeaderName(header.substring(0, boundary).trim()); 29 | value = header.substring(boundary + 1).trim(); 30 | if (headers[name]) { 31 | if (Array.isArray(headers[name])) { 32 | // add to an existing array 33 | headers[name].push(value); 34 | } 35 | else { 36 | // convert single value to array 37 | headers[name] = [headers[name], value]; 38 | } 39 | } 40 | else { 41 | // new, single value 42 | headers[name] = value; 43 | } 44 | }); 45 | 46 | return headers; 47 | } 48 | 49 | function safeMixin(target, source) { 50 | Object.keys(source || {}).forEach(function (prop) { 51 | // make sure the property already exists as 52 | // IE 6 will blow up if we add a new prop 53 | if (source.hasOwnProperty(prop) && prop in target) { 54 | try { 55 | target[prop] = source[prop]; 56 | } 57 | catch (e) { 58 | // ignore, expected for some properties at some points in the request lifecycle 59 | } 60 | } 61 | }); 62 | 63 | return target; 64 | } 65 | 66 | module.exports = client(function xhr(request) { 67 | return responsePromise.promise(function (resolve, reject) { 68 | /*jshint maxcomplexity:20 */ 69 | 70 | var client, method, url, headers, entity, headerName, response, XHR; 71 | 72 | request = typeof request === 'string' ? { path: request } : request || {}; 73 | response = { request: request }; 74 | 75 | if (request.canceled) { 76 | response.error = 'precanceled'; 77 | reject(response); 78 | return; 79 | } 80 | 81 | XHR = request.engine || XMLHttpRequest; 82 | if (!XHR) { 83 | reject({ request: request, error: 'xhr-not-available' }); 84 | return; 85 | } 86 | 87 | entity = request.entity; 88 | request.method = request.method || (entity ? 'POST' : 'GET'); 89 | method = request.method; 90 | url = response.url = request.path || ''; 91 | 92 | try { 93 | client = response.raw = new XHR(); 94 | 95 | // mixin extra request properties before and after opening the request as some properties require being set at different phases of the request 96 | safeMixin(client, request.mixin); 97 | client.open(method, url, true); 98 | safeMixin(client, request.mixin); 99 | 100 | headers = request.headers; 101 | for (headerName in headers) { 102 | /*jshint forin:false */ 103 | if (headerName === 'Content-Type' && headers[headerName] === 'multipart/form-data') { 104 | // XMLHttpRequest generates its own Content-Type header with the 105 | // appropriate multipart boundary when sending multipart/form-data. 106 | continue; 107 | } 108 | 109 | client.setRequestHeader(headerName, headers[headerName]); 110 | } 111 | 112 | request.canceled = false; 113 | request.cancel = function cancel() { 114 | request.canceled = true; 115 | client.abort(); 116 | reject(response); 117 | }; 118 | 119 | client.onreadystatechange = function (/* e */) { 120 | if (request.canceled) { return; } 121 | if (client.readyState === (XHR.DONE || 4)) { 122 | response.status = { 123 | code: client.status, 124 | text: client.statusText 125 | }; 126 | response.headers = parseHeaders(client.getAllResponseHeaders()); 127 | response.entity = client.responseText; 128 | 129 | // #125 -- Sometimes IE8-9 uses 1223 instead of 204 130 | // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request 131 | if (response.status.code === 1223) { 132 | response.status.code = 204; 133 | } 134 | 135 | if (response.status.code > 0) { 136 | // check status code as readystatechange fires before error event 137 | resolve(response); 138 | } 139 | else { 140 | // give the error callback a chance to fire before resolving 141 | // requests for file:// URLs do not have a status code 142 | setTimeout(function () { 143 | resolve(response); 144 | }, 0); 145 | } 146 | } 147 | }; 148 | 149 | try { 150 | client.onerror = function (/* e */) { 151 | response.error = 'loaderror'; 152 | reject(response); 153 | }; 154 | } 155 | catch (e) { 156 | // IE 6 will not support error handling 157 | } 158 | 159 | client.send(entity); 160 | } 161 | catch (e) { 162 | response.error = 'loaderror'; 163 | reject(response); 164 | } 165 | 166 | }); 167 | }); 168 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # rest.js Documentation 2 | 3 | 4 | ## Core Facilities 5 | 6 | [Interceptors](interceptors.md) 7 | 8 | How interceptors are applied to clients with configuration. Descriptions for all interceptors provided with rest.js including configuration properties. How to create custom interceptors using the interceptor factory with links to real interceptors that demonstrate different interceptor characteristics. 9 | 10 | [Clients](clients.md) 11 | 12 | Description of built in clients including the default client and client specific properties. 13 | 14 | [Common Interfaces](interfaces.md) 15 | 16 | Common methods and properties for request/response objects, clients and interceptors. 17 | 18 | 19 | ## RESTful Concepts 20 | 21 | [Content Negotiation](mime.md) 22 | 23 | Support for serializing/deserializing objects to/from HTTP requests/responses. 24 | 25 | 26 | ## Optional Libraries 27 | 28 | [wire.js](wire.md) 29 | 30 | Declarative client configuration with wire.js. 31 | -------------------------------------------------------------------------------- /docs/clients.md: -------------------------------------------------------------------------------- 1 | # Clients 2 | 3 | - Provided Clients 4 | - [Default Client](#module-rest) 5 | - [XMLHttpReqest Client](#module-rest/client/xhr) 6 | - [Node Client](#module-rest/client/node) 7 | - [JSONP Client](#module-rest/client/jsonp) 8 | 9 | 10 | ## Overview 11 | 12 | A rest.js [client](interfaces.md#interface-client) is simply a function that accepts an argument as the [request](interfaces.md#interface-request) and returns a promise for the [response](interfaces.md#interface-response). 13 | 14 | Clients are typically extended by wrapping interceptors that wrap the client core behavior providing additional functionality and returning an enriched client. 15 | 16 | ```javascript 17 | client = rest.wrap(interceptor); 18 | assert.same(rest, client.skip()); 19 | ``` 20 | 21 | See the [interceptor docs](interceptors.md) for more information on interceptors and wrapping. 22 | 23 | 24 | ## Provided Clients 25 | 26 | The provided clients are the root of the interceptor chain. They are responsible for the lowest level mechanics of making requests and handling responses. In most cases, the developer doesn't need to be concerned with the particulars of the client, as the best client for the available environment will be chosen automatically. 27 | 28 | 29 | 30 | ### Default Client 31 | 32 | `rest` ([src](../rest.js)) 33 | 34 | The default client is also the main module for the rest.js package. It's not a client implementation, but an alias to the best client for a platform. When running within a browser, the XHR client is used; when running within Node.js, the Node client is used. As other JavaScript environments are supported, the default client will continue to map directly to the most appropriate client implementation. 35 | 36 | The default client is used internally when no custom client is configured. There are times, when it's useful to change the default client; such as when the automatic environment sniffing is wrong, or you want to add support for a new environment that rest.js doesn't yet understand. In these cases, you can set, get and reset the default client using the `rest.setDefaultClient(client)`, `rest.getDefaultClient()` and `rest.resetDefaultClient()` methods respectively. 37 | 38 | While it may be tempting to change the default client for application level concerns, changing the default will impact all consumers. In just about every case, using an [Interceptor](./interceptors.md) is preferred. 39 | 40 | 41 | 42 | ### XMLHttpReqest Client 43 | 44 | `rest/client/xhr` ([src](../client/xhr.js)) 45 | 46 | The default client for browsers. The XHR client utilizes the XMLHttpRequest object provided by many browsers. 47 | 48 | **Special Properties** 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
PropertyRequired?DefaultDescription
request.engineoptionalwindow.XMLHttpRequestThe XMLHttpRequest instance to use
request.mixinoptionalnoneAdditional properties to mix into the XHR object
70 | 71 | **Know limitations** 72 | 73 | The XHR client has the same security restrictions as the traditional XMLHttpRequest object. For browsers that support XHR v1, that means that requests may only be made to the same origin as the web page. The origin being defined by the scheme, host and port. XHR v2 clients have support for Cross-origin Resource Sharing (CORS). CORS enabled clients have the ability to make requests to any HTTP based service assuming the server is willing to participate in the [CORS dance](http://www.html5rocks.com/en/tutorials/cors/). 74 | 75 | 76 | 77 | ### Node Client 78 | 79 | `rest/client/node` ([src](../client/node.js)) 80 | 81 | The default client for Node.js. The Node client uses the 'http' and 'https' modules. 82 | 83 | Node specific settings may be modified via the `mixin` request property. Adding new certificate authorities to trust or changing the agent pool are rare, but sometimes necessary. See the [Node docs](http://nodejs.org/api/https.html#https_https_request_options_callback) for details about supported properties. 84 | 85 | **Special Properties** 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
PropertyRequired?DefaultDescription
request.mixinoptionalemptyAdditional Node.js only parameters
101 | 102 | 103 | 104 | ### JSONP Client 105 | 106 | `rest/client/jsonp` ([src](../client/jsonp.js)) 107 | 108 | JSONP client for browsers. Allows basic cross-origin GETs via script tags. This client is typically employed via the `rest/interceptor/jsonp` interceptor. Never used as the default client. 109 | 110 | **Special Properties** 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
PropertyRequired?DefaultDescription
request.callback.paramoptional'callback'URL parameter that contains the JSONP callback function's name
request.callback.prefixoptional'jsonp'common prefix for callback function names as they are placed on the window object
request.callback.nameoptionalgeneratedpins the name of the callback function, useful for cases where the server doesn't allow custom callback names. Generally not recommended.
138 | -------------------------------------------------------------------------------- /docs/wire.md: -------------------------------------------------------------------------------- 1 | # wire.js 2 | 3 | [wire.js](https://github.com/cujojs/wire/) is an Inversion of Control container that allows applications to be composed together at runtime based on a declarative configuration. A rest.js plugin is provided for wire.js that enables declarative configuration of rest.js clients, including chaning interceptors with their configuration. 4 | 5 | 6 | 7 | ## Wire Plugin 8 | 9 | `rest/wire` ([src](../wire.js)) 10 | 11 | **TIP:** In each of these examples, `{ module: 'rest/wire' }` is loaded as it provides the 'rest' factory to the wire.js spec. Without this module being loaded into the spec, the facilities below will silently fail. 12 | 13 | 14 | 15 | ### 'rest' Factory 16 | 17 | The `rest` factory provides a declarative way to define a client with an interceptor chain that is nearly identical in capability to imperative JavaScript. The factory access two main config properties, a parent client, and an array of interceptors. Each entry in the interceptor array contains a reference to the interceptor module, and the configuration for that interceptor. The array of interceptors is chained off the client in order returning the resulting client as the wire.js component. 18 | 19 | In it's basic form, the array of interceptors is processed in order, wrapping the parent client. 20 | 21 | ```javascript 22 | client: { 23 | rest: { 24 | parent: { $ref: 'baseClient' }, 25 | interceptors: [ 26 | { module: 'rest/interceptor/mime', config: { mime: 'application/json' } }, 27 | { module: 'rest/interceptor/location' }, 28 | { module: 'rest/interceptor/entity' }, 29 | { module: 'rest/interceptor/hateoas', config: { target: '' } } 30 | ] 31 | } 32 | }, 33 | baseClient: { module: 'rest' }, 34 | $plugins: [{ module: 'rest/wire' }] 35 | ``` 36 | 37 | If parent is not defined, or is not a function, the default client is used as the parent. In that case, the interceptors array can replace the whole factory object 38 | 39 | ```javascript 40 | client: { 41 | rest: [ 42 | { module: 'rest/interceptor/mime', config: { mime: 'application/json' } }, 43 | { module: 'rest/interceptor/location' }, 44 | { module: 'rest/interceptor/entity' }, 45 | { module: 'rest/interceptor/hateoas', config: { target: '' } } 46 | ] 47 | }, 48 | $plugins: [{ module: 'rest/wire' }] 49 | ``` 50 | 51 | If a configuration element isn't needed, a string can be provided to represent the module 52 | 53 | ```javascript 54 | client: { 55 | rest: [ 56 | { module: 'rest/interceptor/mime', config: { mime: 'application/json' } }, 57 | 'rest/interceptor/location', 58 | 'rest/interceptor/entity', 59 | { module: 'rest/interceptor/hateoas', config: { target: '' } } 60 | ] 61 | }, 62 | $plugins: [{ module: 'rest/wire' }] 63 | ``` 64 | 65 | An individual interceptors array entry can use any facility available within wire.js, including $ref. 66 | 67 | ```javascript 68 | client: { 69 | rest: [ 70 | { $ref: 'mime', config: { mime: 'application/json' } }, 71 | 'rest/interceptor/location', 72 | 'rest/interceptor/entity', 73 | { $ref: 'hateoas', config: { target: '' } } 74 | ] 75 | }, 76 | mime: { module: 'rest/interceptor/mime' }, 77 | hateoas: { module: 'rest/interceptor/hateoas' }, 78 | $plugins: [{ module: 'rest/wire' }] 79 | ``` 80 | 81 | The 'config' object for an interceptor may also use any wire.js facility. If a literal config object is desired, but is being wired in an undesirable way, use the 'literal' wire.js factory to provide the literal config. 82 | 83 | ```javascript 84 | client: { 85 | rest: [ 86 | { $ref: 'myInterceptor', config: { literal: { module: 'not/a/wire/module/factory' } } }, 87 | ] 88 | }, 89 | myInterceptor: { ... }, 90 | $plugins: [{ module: 'rest/wire' }] 91 | ``` 92 | -------------------------------------------------------------------------------- /interceptor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var defaultClient, mixin, responsePromise, client; 11 | 12 | defaultClient = require('./client/default'); 13 | mixin = require('./util/mixin'); 14 | responsePromise = require('./util/responsePromise'); 15 | client = require('./client'); 16 | 17 | /** 18 | * Interceptors have the ability to intercept the request and/org response 19 | * objects. They may augment, prune, transform or replace the 20 | * request/response as needed. Clients may be composed by wrapping 21 | * together multiple interceptors. 22 | * 23 | * Configured interceptors are functional in nature. Wrapping a client in 24 | * an interceptor will not affect the client, merely the data that flows in 25 | * and out of that client. A common configuration can be created once and 26 | * shared; specialization can be created by further wrapping that client 27 | * with custom interceptors. 28 | * 29 | * @param {Client} [target] client to wrap 30 | * @param {Object} [config] configuration for the interceptor, properties will be specific to the interceptor implementation 31 | * @returns {Client} A client wrapped with the interceptor 32 | * 33 | * @class Interceptor 34 | */ 35 | 36 | function defaultInitHandler(config) { 37 | return config; 38 | } 39 | 40 | function defaultRequestHandler(request /*, config, meta */) { 41 | return request; 42 | } 43 | 44 | function defaultResponseHandler(response /*, config, meta */) { 45 | return response; 46 | } 47 | 48 | /** 49 | * Alternate return type for the request handler that allows for more complex interactions. 50 | * 51 | * @param properties.request the traditional request return object 52 | * @param {Promise} [properties.abort] promise that resolves if/when the request is aborted 53 | * @param {Client} [properties.client] override the defined client with an alternate client 54 | * @param [properties.response] response for the request, short circuit the request 55 | */ 56 | function ComplexRequest(properties) { 57 | if (!(this instanceof ComplexRequest)) { 58 | // in case users forget the 'new' don't mix into the interceptor 59 | return new ComplexRequest(properties); 60 | } 61 | mixin(this, properties); 62 | } 63 | 64 | /** 65 | * Create a new interceptor for the provided handlers. 66 | * 67 | * @param {Function} [handlers.init] one time intialization, must return the config object 68 | * @param {Function} [handlers.request] request handler 69 | * @param {Function} [handlers.response] response handler regardless of error state 70 | * @param {Function} [handlers.success] response handler when the request is not in error 71 | * @param {Function} [handlers.error] response handler when the request is in error, may be used to 'unreject' an error state 72 | * @param {Function} [handlers.client] the client to use if otherwise not specified, defaults to platform default client 73 | * 74 | * @returns {Interceptor} 75 | */ 76 | function interceptor(handlers) { 77 | 78 | var initHandler, requestHandler, successResponseHandler, errorResponseHandler; 79 | 80 | handlers = handlers || {}; 81 | 82 | initHandler = handlers.init || defaultInitHandler; 83 | requestHandler = handlers.request || defaultRequestHandler; 84 | successResponseHandler = handlers.success || handlers.response || defaultResponseHandler; 85 | errorResponseHandler = handlers.error || function () { 86 | // Propagate the rejection, with the result of the handler 87 | return Promise.resolve((handlers.response || defaultResponseHandler).apply(this, arguments)) 88 | .then(Promise.reject.bind(Promise)); 89 | }; 90 | 91 | return function (target, config) { 92 | 93 | if (typeof target === 'object') { 94 | config = target; 95 | } 96 | if (typeof target !== 'function') { 97 | target = handlers.client || defaultClient; 98 | } 99 | 100 | config = initHandler(config || {}); 101 | 102 | function interceptedClient(request) { 103 | var context, meta; 104 | context = {}; 105 | meta = { 'arguments': Array.prototype.slice.call(arguments), client: interceptedClient }; 106 | request = typeof request === 'string' ? { path: request } : request || {}; 107 | request.originator = request.originator || interceptedClient; 108 | return responsePromise( 109 | requestHandler.call(context, request, config, meta), 110 | function (request) { 111 | var response, abort, next; 112 | next = target; 113 | if (request instanceof ComplexRequest) { 114 | // unpack request 115 | abort = request.abort; 116 | next = request.client || next; 117 | response = request.response; 118 | // normalize request, must be last 119 | request = request.request; 120 | } 121 | response = response || Promise.resolve(request).then(function (request) { 122 | return Promise.resolve(next(request)).then( 123 | function (response) { 124 | return successResponseHandler.call(context, response, config, meta); 125 | }, 126 | function (response) { 127 | return errorResponseHandler.call(context, response, config, meta); 128 | } 129 | ); 130 | }); 131 | return abort ? Promise.race([response, abort]) : response; 132 | }, 133 | function (error) { 134 | return Promise.reject({ request: request, error: error }); 135 | } 136 | ); 137 | } 138 | 139 | return client(interceptedClient, target); 140 | }; 141 | } 142 | 143 | interceptor.ComplexRequest = ComplexRequest; 144 | 145 | module.exports = interceptor; 146 | -------------------------------------------------------------------------------- /interceptor/basicAuth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, base64; 11 | 12 | interceptor = require('../interceptor'); 13 | base64 = require('../util/base64'); 14 | 15 | /** 16 | * Authenticates the request using HTTP Basic Authentication (rfc2617) 17 | * 18 | * @param {Client} [client] client to wrap 19 | * @param {string} config.username username 20 | * @param {string} [config.password=''] password for the user 21 | * 22 | * @returns {Client} 23 | */ 24 | module.exports = interceptor({ 25 | request: function handleRequest(request, config) { 26 | var headers, username, password; 27 | 28 | headers = request.headers || (request.headers = {}); 29 | username = request.username || config.username; 30 | password = request.password || config.password || ''; 31 | 32 | if (username) { 33 | headers.Authorization = 'Basic ' + base64.encode(username + ':' + password); 34 | } 35 | 36 | return request; 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /interceptor/csrf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | /** 15 | * Applies a Cross-Site Request Forgery protection header to a request 16 | * 17 | * CSRF protection helps a server verify that a request came from a 18 | * trusted client and not another client that was able to masquerade 19 | * as an authorized client. Sites that use cookie based authentication 20 | * are particularly vulnerable to request forgeries without extra 21 | * protection. 22 | * 23 | * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery 24 | * 25 | * @param {Client} [client] client to wrap 26 | * @param {string} [config.name='X-Csrf-Token'] name of the request 27 | * header, may be overridden by `request.csrfTokenName` 28 | * @param {string} [config.token] CSRF token, may be overridden by 29 | * `request.csrfToken` 30 | * 31 | * @returns {Client} 32 | */ 33 | module.exports = interceptor({ 34 | init: function (config) { 35 | config.name = config.name || 'X-Csrf-Token'; 36 | return config; 37 | }, 38 | request: function handleRequest(request, config) { 39 | var headers, name, token; 40 | 41 | headers = request.headers || (request.headers = {}); 42 | name = request.csrfTokenName || config.name; 43 | token = request.csrfToken || config.token; 44 | 45 | if (token) { 46 | headers[name] = token; 47 | } 48 | 49 | return request; 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /interceptor/defaultRequest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, mixinUtil, defaulter; 11 | 12 | interceptor = require('../interceptor'); 13 | mixinUtil = require('../util/mixin'); 14 | 15 | defaulter = (function () { 16 | 17 | function mixin(prop, target, defaults) { 18 | if (prop in target || prop in defaults) { 19 | target[prop] = mixinUtil({}, defaults[prop], target[prop]); 20 | } 21 | } 22 | 23 | function copy(prop, target, defaults) { 24 | if (prop in defaults && !(prop in target)) { 25 | target[prop] = defaults[prop]; 26 | } 27 | } 28 | 29 | var mappings = { 30 | method: copy, 31 | path: copy, 32 | params: mixin, 33 | headers: mixin, 34 | entity: copy, 35 | mixin: mixin 36 | }; 37 | 38 | return function (target, defaults) { 39 | for (var prop in mappings) { 40 | /*jshint forin: false */ 41 | mappings[prop](prop, target, defaults); 42 | } 43 | return target; 44 | }; 45 | 46 | }()); 47 | 48 | /** 49 | * Provide default values for a request. These values will be applied to the 50 | * request if the request object does not already contain an explicit value. 51 | * 52 | * For 'params', 'headers', and 'mixin', individual values are mixed in with the 53 | * request's values. The result is a new object representiing the combined 54 | * request and config values. Neither input object is mutated. 55 | * 56 | * @param {Client} [client] client to wrap 57 | * @param {string} [config.method] the default method 58 | * @param {string} [config.path] the default path 59 | * @param {Object} [config.params] the default params, mixed with the request's existing params 60 | * @param {Object} [config.headers] the default headers, mixed with the request's existing headers 61 | * @param {Object} [config.mixin] the default "mixins" (http/https options), mixed with the request's existing "mixins" 62 | * 63 | * @returns {Client} 64 | */ 65 | module.exports = interceptor({ 66 | request: function handleRequest(request, config) { 67 | return defaulter(request, config); 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /interceptor/entity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | if (typeof console !== 'undefined') { 15 | console.log('rest.js: rest/interceptor/entity is deprecated, please use response.entity() instead'); 16 | } 17 | 18 | /** 19 | * @deprecated use response.entity() instead 20 | * 21 | * Returns the response entity as the response, discarding other response 22 | * properties. 23 | * 24 | * @param {Client} [client] client to wrap 25 | * 26 | * @returns {Client} 27 | */ 28 | module.exports = interceptor({ 29 | response: function (response) { 30 | if ('entity' in response) { 31 | return response.entity; 32 | } 33 | return response; 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /interceptor/errorCode.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | /** 15 | * Rejects the response promise based on the status code. 16 | * 17 | * Codes greater than or equal to the provided value are rejected. Default 18 | * value 400. 19 | * 20 | * @param {Client} [client] client to wrap 21 | * @param {number} [config.code=400] code to indicate a rejection 22 | * 23 | * @returns {Client} 24 | */ 25 | module.exports = interceptor({ 26 | init: function (config) { 27 | config.code = config.code || 400; 28 | return config; 29 | }, 30 | response: function (response, config) { 31 | if (response.status && response.status.code >= config.code) { 32 | return Promise.reject(response); 33 | } 34 | return response; 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /interceptor/hateoas.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, pathPrefix, rfc5988LinkParser, find; 11 | 12 | interceptor = require('../interceptor'); 13 | pathPrefix = require('./pathPrefix'); 14 | rfc5988LinkParser = require('../parsers/rfc5988'); 15 | find = require('../util/find'); 16 | 17 | /** 18 | * [Experimental] 19 | * 20 | * Supports 'Hypertext As The Engine Of Application State' style 21 | * services by indexing the 'links' property from the entity to make 22 | * accessing links via the 'rel' attribute easier. 23 | * 24 | * Links are index in two ways: 25 | * 1. as link's 'rel' which when accessed issues a request for the 26 | * linked resource. A promise for the related resourse is expected 27 | * to be returned. 28 | * 2. as link's 'rel' with 'Link' appended, as a reference to the link 29 | * object 30 | * 31 | * The 'Link' response header is also parsed for related resources 32 | * following rfc5988. The values parsed from the headers are indexed 33 | * into the response.links object. 34 | * 35 | * Also defines a 'clientFor' factory function that creates a new 36 | * client configured to communicate with a related resource. 37 | * 38 | * The client for the resoruce reference and the 'clientFor' function 39 | * can be provided by the 'client' config property. 40 | * 41 | * Index links are exposed by default on the entity. A child object may be 42 | * configed by the 'target' config property. 43 | * 44 | * @param {Client} [client] client to wrap 45 | * @param {string} [config.target=''] property to create on the entity and 46 | * parse links into. If empty, the response entity is used directly. 47 | * @param {Client} [config.client=request.originator] the parent client to 48 | * use when creating clients for a linked resources. Defaults to the 49 | * request's originator if available, otherwise the current interceptor's 50 | * client 51 | * 52 | * @returns {Client} 53 | */ 54 | module.exports = interceptor({ 55 | init: function (config) { 56 | config.target = config.target || ''; 57 | return config; 58 | }, 59 | response: function (response, config, meta) { 60 | var client; 61 | 62 | client = config.client || (response.request && response.request.originator) || meta.client; 63 | 64 | function apply(target, links) { 65 | links.forEach(function (link) { 66 | Object.defineProperty(target, link.rel + 'Link', { 67 | enumerable: false, 68 | configurable: true, 69 | value: link 70 | }); 71 | Object.defineProperty(target, link.rel, { 72 | enumerable: false, 73 | configurable: true, 74 | get: function () { 75 | var response = client({ path: link.href }); 76 | Object.defineProperty(target, link.rel, { 77 | enumerable: false, 78 | configurable: true, 79 | value: response 80 | }); 81 | return response; 82 | } 83 | }); 84 | }); 85 | 86 | // if only Proxy was well supported... 87 | Object.defineProperty(target, 'clientFor', { 88 | enumerable: false, 89 | value: function clientFor(rel, parentClient) { 90 | return pathPrefix( 91 | parentClient || client, 92 | { prefix: target[rel + 'Link'].href } 93 | ); 94 | } 95 | }); 96 | } 97 | 98 | function parseLinkHeaders(headers) { 99 | var links = []; 100 | [].concat(headers).forEach(function (header) { 101 | try { 102 | links = links.concat(rfc5988LinkParser.parse(header)); 103 | } 104 | catch (e) { 105 | // ignore 106 | // TODO consider a debug mode that logs 107 | } 108 | }); 109 | return links; 110 | } 111 | 112 | if (response.headers && response.headers.Link) { 113 | response.links = response.links || {}; 114 | apply(response.links, parseLinkHeaders(response.headers.Link)); 115 | } 116 | 117 | find.findProperties(response.entity, 'links', function (obj, host) { 118 | var target; 119 | 120 | if (Array.isArray(host.links)) { 121 | if (config.target === '') { 122 | target = host; 123 | } 124 | else { 125 | target = {}; 126 | Object.defineProperty(host, config.target, { 127 | enumerable: false, 128 | value: target 129 | }); 130 | } 131 | 132 | apply(target, host.links); 133 | } 134 | }); 135 | 136 | return response; 137 | } 138 | }); 139 | -------------------------------------------------------------------------------- /interceptor/jsonp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, jsonpClient; 11 | 12 | interceptor = require('../interceptor'); 13 | jsonpClient = require('../client/jsonp'); 14 | 15 | /** 16 | * Allows common configuration of JSONP clients. 17 | * 18 | * Values provided to this interceptor are added to the request, if the 19 | * request dose not already contain the property. 20 | * 21 | * The rest/client/jsonp client is used by default instead of the 22 | * common default client for the platform. 23 | * 24 | * @param {Client} [client=rest/client/jsonp] custom client to wrap 25 | * @param {string} [config.callback.param] the parameter name for which the 26 | * callback function name is the value 27 | * @param {string} [config.callback.prefix] prefix for the callback function, 28 | * as the callback is attached to the window object, a unique, unobtrusive 29 | * prefix is desired 30 | * @param {string} [request.callback.name=] pins the name of the 31 | * callback function, useful for cases where the server doesn't allow 32 | * custom callback names. Generally not recommended. 33 | * 34 | * @returns {Client} 35 | */ 36 | module.exports = interceptor({ 37 | client: jsonpClient, 38 | init: function (config) { 39 | config.callback = config.callback || {}; 40 | return config; 41 | }, 42 | request: function (request, config) { 43 | request.callback = request.callback || {}; 44 | request.callback.param = request.callback.param || config.callback.param; 45 | request.callback.prefix = request.callback.prefix || config.callback.prefix; 46 | request.callback.name = request.callback.name || config.callback.name; 47 | return request; 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /interceptor/location.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | function isRedirect(response, config) { 15 | var matchesRedirectCode = config.code === 0 || (response.status && response.status.code >= config.code); 16 | return response.headers && response.headers.Location && matchesRedirectCode; 17 | } 18 | 19 | /** 20 | * Follows the Location header in a response, if present. The response 21 | * returned is for the subsequent request. 22 | * 23 | * Most browsers will automatically follow HTTP 3xx redirects, however, 24 | * they will not automatically follow 2xx locations. 25 | * 26 | * @param {Client} [client] client to wrap 27 | * @param {Client} [config.client=request.originator] client to use for subsequent request 28 | * 29 | * @returns {Client} 30 | */ 31 | module.exports = interceptor({ 32 | init: function (config) { 33 | config.code = config.code || 0; 34 | return config; 35 | }, 36 | success: function (response, config, client) { 37 | var request; 38 | 39 | if (isRedirect(response, config)) { 40 | request = response.request || {}; 41 | client = (config.client || request.originator || client.skip()); 42 | 43 | return client({ 44 | method: 'GET', 45 | path: response.headers.Location 46 | }); 47 | } 48 | 49 | return response; 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /interceptor/mime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, mime, registry, noopConverter, missingConverter, attempt; 11 | 12 | interceptor = require('../interceptor'); 13 | mime = require('../mime'); 14 | registry = require('../mime/registry'); 15 | attempt = require('../util/attempt'); 16 | 17 | noopConverter = { 18 | read: function (obj) { return obj; }, 19 | write: function (obj) { return obj; } 20 | }; 21 | 22 | missingConverter = { 23 | read: function () { throw 'No read method found on converter'; }, 24 | write: function () { throw 'No write method found on converter'; } 25 | }; 26 | 27 | /** 28 | * MIME type support for request and response entities. Entities are 29 | * (de)serialized using the converter for the MIME type. 30 | * 31 | * Request entities are converted using the desired converter and the 32 | * 'Accept' request header prefers this MIME. 33 | * 34 | * Response entities are converted based on the Content-Type response header. 35 | * 36 | * @param {Client} [client] client to wrap 37 | * @param {string} [config.mime='text/plain'] MIME type to encode the request 38 | * entity 39 | * @param {string} [config.accept] Accept header for the request 40 | * @param {Client} [config.client=] client passed to the 41 | * converter, defaults to the client originating the request 42 | * @param {Registry} [config.registry] MIME registry, defaults to the root 43 | * registry 44 | * @param {boolean} [config.permissive] Allow an unkown request MIME type 45 | * 46 | * @returns {Client} 47 | */ 48 | module.exports = interceptor({ 49 | init: function (config) { 50 | config.registry = config.registry || registry; 51 | return config; 52 | }, 53 | request: function (request, config) { 54 | var type, headers; 55 | 56 | headers = request.headers || (request.headers = {}); 57 | type = mime.parse(headers['Content-Type'] || config.mime || 'text/plain'); 58 | headers.Accept = headers.Accept || config.accept || type.raw + ', application/json;q=0.8, text/plain;q=0.5, */*;q=0.2'; 59 | 60 | if (!('entity' in request)) { 61 | return request; 62 | } 63 | 64 | headers['Content-Type'] = type.raw; 65 | 66 | return config.registry.lookup(type)['catch'](function () { 67 | // failed to resolve converter 68 | if (config.permissive) { 69 | return noopConverter; 70 | } 71 | throw 'mime-unknown'; 72 | }).then(function (converter) { 73 | var client = config.client || request.originator, 74 | write = converter.write || missingConverter.write; 75 | 76 | return attempt(write.bind(void 0, request.entity, { client: client, request: request, mime: type, registry: config.registry })) 77 | ['catch'](function() { 78 | throw 'mime-serialization'; 79 | }) 80 | .then(function(entity) { 81 | request.entity = entity; 82 | return request; 83 | }); 84 | }); 85 | }, 86 | response: function (response, config) { 87 | if (!(response.headers && response.headers['Content-Type'] && response.entity)) { 88 | return response; 89 | } 90 | 91 | var type = mime.parse(response.headers['Content-Type']); 92 | 93 | return config.registry.lookup(type)['catch'](function () { return noopConverter; }).then(function (converter) { 94 | var client = config.client || response.request && response.request.originator, 95 | read = converter.read || missingConverter.read; 96 | 97 | return attempt(read.bind(void 0, response.entity, { client: client, response: response, mime: type, registry: config.registry })) 98 | ['catch'](function (e) { 99 | response.error = 'mime-deserialization'; 100 | response.cause = e; 101 | throw response; 102 | }) 103 | .then(function (entity) { 104 | response.entity = entity; 105 | return response; 106 | }); 107 | }); 108 | } 109 | }); 110 | -------------------------------------------------------------------------------- /interceptor/oAuth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, UrlBuilder, pubsub; 11 | 12 | interceptor = require('../interceptor'); 13 | UrlBuilder = require('../UrlBuilder'); 14 | pubsub = require('../util/pubsub'); 15 | 16 | function defaultOAuthCallback(hash) { 17 | var params, queryString, regex, m; 18 | 19 | queryString = hash.indexOf('#') === 0 ? hash.substring(1) : hash; 20 | params = {}; 21 | regex = /([^&=]+)=([^&]*)/g; 22 | 23 | m = regex.exec(queryString); 24 | do { 25 | params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); 26 | m = regex.exec(queryString); 27 | } while (m); 28 | 29 | /*jshint camelcase:false */ 30 | pubsub.publish(params.state, params.token_type + ' ' + params.access_token); 31 | } 32 | 33 | function defaultWindowStrategy(url) { 34 | var w = window.open(url, '_blank', 'width=500,height=400'); 35 | return function () { 36 | w.close(); 37 | }; 38 | } 39 | 40 | function authorize(config) { 41 | var state, url, dismissWindow; 42 | 43 | return new Promise(function (resolve) { 44 | 45 | state = Math.random() * new Date().getTime(); 46 | url = new UrlBuilder(config.authorizationUrlBase).build({ 47 | 'response_type': 'token', 48 | 'redirect_uri': config.redirectUrl, 49 | 'client_id': config.clientId, 50 | 'scope': config.scope, 51 | 'state': state 52 | }); 53 | 54 | dismissWindow = config.windowStrategy(url); 55 | 56 | pubsub.subscribe(state, function (authorization) { 57 | dismissWindow(); 58 | resolve(authorization); 59 | }); 60 | 61 | }); 62 | } 63 | 64 | /** 65 | * OAuth implicit flow support 66 | * 67 | * Authorizes request with the OAuth authorization token. Tokens are 68 | * requested from the authorization server as needed if there isn't a 69 | * token, or the token is expired. 70 | * 71 | * A custom window strategy can be provided to replace the default popup 72 | * window. The window strategy is a function that must accept a URL as an 73 | * argument and returns a function to close and cleanup the window. A 74 | * common custom strategy would be to use an iframe in a dialog. 75 | * 76 | * The callback function must be invoked when the authorization server 77 | * redirects the browser back to the application. 78 | * 79 | * NOTE: Registering a handler to receive the redirect is required and 80 | * outside the scope of this interceptor. The implementer must collect the 81 | * URL fragment and pass it to the callback function on the 'opener', or 82 | * 'parent' window. 83 | * 84 | * @param {Client} [target] client to wrap 85 | * @param {string} [config.token] pre-configured authentication token 86 | * @param {string} config.clientId OAuth clientId 87 | * @param {string} config.scope OAuth scope 88 | * @param {string} config.authorizationUrlBase URL of the authorization server 89 | * @param {string} [config.redirectUrl] callback URL from the authorization server. Will be converted to a fully qualified, absolute URL, if needed. Default's to the window's location or base href. 90 | * @param {Function} [config.windowStrategy] strategy for opening the authorization window, defaults to window.open 91 | * @param {string} [config.oAuthCallbackName='oAuthCallback'] name to register the callback as in global scope 92 | * @param {Function} [config.oAuthCallback] callback function to receive OAuth URL fragment 93 | * 94 | * @returns {Client} 95 | */ 96 | module.exports = interceptor({ 97 | init: function (config) { 98 | config.redirectUrl = new UrlBuilder(config.redirectUrl).fullyQualify().build(); 99 | config.windowStrategy = config.windowStrategy || defaultWindowStrategy; 100 | config.oAuthCallback = config.oAuthCallback || defaultOAuthCallback; 101 | config.oAuthCallbackName = config.oAuthCallbackName || 'oAuthCallback'; 102 | 103 | window[config.oAuthCallbackName] = config.oAuthCallback; 104 | 105 | return config; 106 | }, 107 | request: function (request, config) { 108 | request.headers = request.headers || {}; 109 | 110 | if (config.token) { 111 | request.headers.Authorization = config.token; 112 | return request; 113 | } 114 | else { 115 | return authorize(config).then(function (authorization) { 116 | request.headers.Authorization = config.token = authorization; 117 | return request; 118 | }); 119 | } 120 | }, 121 | response: function (response, config, meta) { 122 | if (response.status.code === 401) { 123 | // token probably expired, reauthorize 124 | return authorize(config).then(function (authorization) { 125 | config.token = authorization; 126 | return meta.client(response.request); 127 | }); 128 | } 129 | else if (response.status.code === 403) { 130 | return Promise.reject(response); 131 | } 132 | 133 | return response; 134 | } 135 | }); 136 | -------------------------------------------------------------------------------- /interceptor/params.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, UrlBuilder; 11 | 12 | interceptor = require('../interceptor'); 13 | UrlBuilder = require('../UrlBuilder'); 14 | 15 | /** 16 | * Applies request params to the path by token replacement 17 | * 18 | * Params not applied as a token are appended to the query string. Params 19 | * are removed from the request object, as they have been consumed. 20 | * 21 | * @deprecated The template interceptor `rest/interceptor/template` is a 22 | * much richer way to apply paramters to a template. This interceptor is 23 | * available as a bridge to users who previousled depended on this 24 | * functionality being available directly on clients. 25 | * 26 | * @param {Client} [client] client to wrap 27 | * @param {Object} [config.params] default param values 28 | * 29 | * @returns {Client} 30 | */ 31 | module.exports = interceptor({ 32 | init: function (config) { 33 | config.params = config.params || {}; 34 | return config; 35 | }, 36 | request: function (request, config) { 37 | var path, params; 38 | 39 | path = request.path || ''; 40 | params = request.params || {}; 41 | 42 | request.path = new UrlBuilder(path, config.params).append('', params).build(); 43 | delete request.params; 44 | 45 | return request; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /interceptor/pathPrefix.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, UrlBuilder; 11 | 12 | interceptor = require('../interceptor'); 13 | UrlBuilder = require('../UrlBuilder'); 14 | 15 | function startsWith(str, prefix) { 16 | return str.indexOf(prefix) === 0; 17 | } 18 | 19 | function endsWith(str, suffix) { 20 | return str.lastIndexOf(suffix) + suffix.length === str.length; 21 | } 22 | 23 | /** 24 | * Prefixes the request path with a common value. 25 | * 26 | * @param {Client} [client] client to wrap 27 | * @param {number} [config.prefix] path prefix 28 | * 29 | * @returns {Client} 30 | */ 31 | module.exports = interceptor({ 32 | request: function (request, config) { 33 | var path; 34 | 35 | if (config.prefix && !(new UrlBuilder(request.path).isFullyQualified())) { 36 | path = config.prefix; 37 | if (request.path) { 38 | if (!endsWith(path, '/') && !startsWith(request.path, '/')) { 39 | // add missing '/' between path sections 40 | path += '/'; 41 | } 42 | path += request.path; 43 | } 44 | request.path = path; 45 | } 46 | 47 | return request; 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /interceptor/retry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Jeremy Grelle 6 | * @author Scott Andrews 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var interceptor, delay; 12 | 13 | interceptor = require('../interceptor'); 14 | delay = require('../util/delay'); 15 | 16 | /** 17 | * Retries a rejected request using an exponential backoff. 18 | * 19 | * Defaults to an initial interval of 100ms, a multiplier of 2, and no max interval. 20 | * 21 | * @param {Client} [client] client to wrap 22 | * @param {number} [config.intial=100] initial interval in ms 23 | * @param {number} [config.multiplier=2] interval multiplier 24 | * @param {number} [config.max] max interval in ms 25 | * 26 | * @returns {Client} 27 | */ 28 | module.exports = interceptor({ 29 | init: function (config) { 30 | config.initial = config.initial || 100; 31 | config.multiplier = config.multiplier || 2; 32 | config.max = config.max || Infinity; 33 | return config; 34 | }, 35 | error: function (response, config, meta) { 36 | var request; 37 | 38 | request = response.request; 39 | request.retry = request.retry || config.initial; 40 | 41 | return delay(request.retry, request).then(function (request) { 42 | if (request.canceled) { 43 | // cancel here in case client doesn't check canceled flag 44 | return Promise.reject({ request: request, error: 'precanceled' }); 45 | } 46 | request.retry = Math.min(request.retry * config.multiplier, config.max); 47 | return meta.client(request); 48 | }); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /interceptor/template.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, uriTemplate, mixin; 11 | 12 | interceptor = require('../interceptor'); 13 | uriTemplate = require('../util/uriTemplate'); 14 | mixin = require('../util/mixin'); 15 | 16 | /** 17 | * Applies request params to the path as a URI Template 18 | * 19 | * Params are removed from the request object, as they have been consumed. 20 | * 21 | * @see https://tools.ietf.org/html/rfc6570 22 | * 23 | * @param {Client} [client] client to wrap 24 | * @param {Object} [config.params] default param values 25 | * @param {string} [config.template] default template 26 | * 27 | * @returns {Client} 28 | */ 29 | module.exports = interceptor({ 30 | init: function (config) { 31 | config.params = config.params || {}; 32 | config.template = config.template || ''; 33 | return config; 34 | }, 35 | request: function (request, config) { 36 | var template, params; 37 | 38 | template = request.path || config.template; 39 | params = mixin({}, request.params, config.params); 40 | 41 | request.path = uriTemplate.expand(template, params); 42 | delete request.params; 43 | 44 | return request; 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /interceptor/timeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Jeremy Grelle 6 | * @author Scott Andrews 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var interceptor; 12 | 13 | interceptor = require('../interceptor'); 14 | 15 | /** 16 | * Cancels a request if it takes longer then the timeout value. 17 | * 18 | * @param {Client} [client] client to wrap 19 | * @param {number} [config.timeout=0] duration in milliseconds before canceling the request. Non-positive values disable the timeout 20 | * @param {boolean} [config.transient=false] if true, timed out requests will not be marked as canceled so that it may be retried 21 | * 22 | * @returns {Client} 23 | */ 24 | module.exports = interceptor({ 25 | init: function (config) { 26 | config.timeout = config.timeout || 0; 27 | config.transient = !!config.transient; 28 | return config; 29 | }, 30 | request: function (request, config) { 31 | var timeout, abort, triggerAbort, transient; 32 | timeout = 'timeout' in request ? request.timeout : config.timeout; 33 | transient = 'transient' in request ? request.transient : config.transient; 34 | if (timeout <= 0) { 35 | return request; 36 | } 37 | abort = new Promise(function (resolve, reject) { 38 | triggerAbort = reject; 39 | }); 40 | this.timeout = setTimeout(function () { 41 | triggerAbort({ request: request, error: 'timeout' }); 42 | if (request.cancel) { 43 | request.cancel(); 44 | if (transient) { 45 | // unmark request as canceled for future requests 46 | request.canceled = false; 47 | } 48 | } 49 | else if (!transient) { 50 | request.canceled = true; 51 | } 52 | }, timeout); 53 | return new interceptor.ComplexRequest({ request: request, abort: abort }); 54 | }, 55 | response: function (response) { 56 | if (this.timeout) { 57 | clearTimeout(this.timeout); 58 | delete this.timeout; 59 | } 60 | return response; 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /mime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Parse a MIME type into it's constituent parts 12 | * 13 | * @param {string} mime MIME type to parse 14 | * @return {{ 15 | * {string} raw the original MIME type 16 | * {string} type the type and subtype 17 | * {string} [suffix] mime suffix, including the plus, if any 18 | * {Object} params key/value pair of attributes 19 | * }} 20 | */ 21 | function parse(mime) { 22 | var params, type; 23 | 24 | params = mime.split(';'); 25 | type = params[0].trim().split('+'); 26 | 27 | return { 28 | raw: mime, 29 | type: type[0], 30 | suffix: type[1] ? '+' + type[1] : '', 31 | params: params.slice(1).reduce(function (params, pair) { 32 | pair = pair.split('='); 33 | params[pair[0].trim()] = pair[1] ? pair[1].trim() : void 0; 34 | return params; 35 | }, {}) 36 | }; 37 | } 38 | 39 | module.exports = { 40 | parse: parse 41 | }; 42 | -------------------------------------------------------------------------------- /mime/registry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var mime, registry; 11 | 12 | mime = require('../mime'); 13 | 14 | function Registry(mimes) { 15 | 16 | /** 17 | * Lookup the converter for a MIME type 18 | * 19 | * @param {string} type the MIME type 20 | * @return a promise for the converter 21 | */ 22 | this.lookup = function lookup(type) { 23 | var parsed; 24 | 25 | parsed = typeof type === 'string' ? mime.parse(type) : type; 26 | 27 | if (mimes[parsed.raw]) { 28 | return mimes[parsed.raw]; 29 | } 30 | if (mimes[parsed.type + parsed.suffix]) { 31 | return mimes[parsed.type + parsed.suffix]; 32 | } 33 | if (mimes[parsed.type]) { 34 | return mimes[parsed.type]; 35 | } 36 | if (mimes[parsed.suffix]) { 37 | return mimes[parsed.suffix]; 38 | } 39 | 40 | return Promise.reject(new Error('Unable to locate converter for mime "' + parsed.raw + '"')); 41 | }; 42 | 43 | /** 44 | * Create a late dispatched proxy to the target converter. 45 | * 46 | * Common when a converter is registered under multiple names and 47 | * should be kept in sync if updated. 48 | * 49 | * @param {string} type mime converter to dispatch to 50 | * @returns converter whose read/write methods target the desired mime converter 51 | */ 52 | this.delegate = function delegate(type) { 53 | return { 54 | read: function () { 55 | var args = arguments; 56 | return this.lookup(type).then(function (converter) { 57 | return converter.read.apply(this, args); 58 | }.bind(this)); 59 | }.bind(this), 60 | write: function () { 61 | var args = arguments; 62 | return this.lookup(type).then(function (converter) { 63 | return converter.write.apply(this, args); 64 | }.bind(this)); 65 | }.bind(this) 66 | }; 67 | }; 68 | 69 | /** 70 | * Register a custom converter for a MIME type 71 | * 72 | * @param {string} type the MIME type 73 | * @param converter the converter for the MIME type 74 | * @return a promise for the converter 75 | */ 76 | this.register = function register(type, converter) { 77 | mimes[type] = Promise.resolve(converter); 78 | return mimes[type]; 79 | }; 80 | 81 | /** 82 | * Create a child registry whoes registered converters remain local, while 83 | * able to lookup converters from its parent. 84 | * 85 | * @returns child MIME registry 86 | */ 87 | this.child = function child() { 88 | return new Registry(Object.create(mimes)); 89 | }; 90 | 91 | } 92 | 93 | registry = new Registry({}); 94 | 95 | // include provided serializers 96 | registry.register('application/hal', require('./type/application/hal')); 97 | registry.register('application/json', require('./type/application/json')); 98 | registry.register('application/x-www-form-urlencoded', require('./type/application/x-www-form-urlencoded')); 99 | registry.register('multipart/form-data', require('./type/multipart/form-data')); 100 | registry.register('text/plain', require('./type/text/plain')); 101 | 102 | registry.register('+json', registry.delegate('application/json')); 103 | 104 | module.exports = registry; 105 | -------------------------------------------------------------------------------- /mime/type/application/hal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var pathPrefix, template, find, lazyPromise, responsePromise; 11 | 12 | pathPrefix = require('../../../interceptor/pathPrefix'); 13 | template = require('../../../interceptor/template'); 14 | find = require('../../../util/find'); 15 | lazyPromise = require('../../../util/lazyPromise'); 16 | responsePromise = require('../../../util/responsePromise'); 17 | 18 | function defineProperty(obj, name, value) { 19 | Object.defineProperty(obj, name, { 20 | value: value, 21 | configurable: true, 22 | enumerable: false, 23 | writeable: true 24 | }); 25 | } 26 | 27 | /** 28 | * Hypertext Application Language serializer 29 | * 30 | * Implemented to https://tools.ietf.org/html/draft-kelly-json-hal-06 31 | * 32 | * As the spec is still a draft, this implementation will be updated as the 33 | * spec evolves 34 | * 35 | * Objects are read as HAL indexing links and embedded objects on to the 36 | * resource. Objects are written as plain JSON. 37 | * 38 | * Embedded relationships are indexed onto the resource by the relationship 39 | * as a promise for the related resource. 40 | * 41 | * Links are indexed onto the resource as a lazy promise that will GET the 42 | * resource when a handler is first registered on the promise. 43 | * 44 | * A `requestFor` method is added to the entity to make a request for the 45 | * relationship. 46 | * 47 | * A `clientFor` method is added to the entity to get a full Client for a 48 | * relationship. 49 | * 50 | * The `_links` and `_embedded` properties on the resource are made 51 | * non-enumerable. 52 | */ 53 | module.exports = { 54 | 55 | read: function (str, opts) { 56 | var client, console; 57 | 58 | opts = opts || {}; 59 | client = opts.client; 60 | console = opts.console || console; 61 | 62 | function deprecationWarning(relationship, deprecation) { 63 | if (deprecation && console && console.warn || console.log) { 64 | (console.warn || console.log).call(console, 'Relationship \'' + relationship + '\' is deprecated, see ' + deprecation); 65 | } 66 | } 67 | 68 | return opts.registry.lookup(opts.mime.suffix).then(function (converter) { 69 | return converter.read(str, opts); 70 | }).then(function (root) { 71 | find.findProperties(root, '_embedded', function (embedded, resource, name) { 72 | Object.keys(embedded).forEach(function (relationship) { 73 | if (relationship in resource) { return; } 74 | var related = responsePromise({ 75 | entity: embedded[relationship] 76 | }); 77 | defineProperty(resource, relationship, related); 78 | }); 79 | defineProperty(resource, name, embedded); 80 | }); 81 | find.findProperties(root, '_links', function (links, resource, name) { 82 | Object.keys(links).forEach(function (relationship) { 83 | var link = links[relationship]; 84 | if (relationship in resource) { return; } 85 | defineProperty(resource, relationship, responsePromise.make(lazyPromise(function () { 86 | if (link.deprecation) { deprecationWarning(relationship, link.deprecation); } 87 | if (link.templated === true) { 88 | return template(client)({ path: link.href }); 89 | } 90 | return client({ path: link.href }); 91 | }))); 92 | }); 93 | defineProperty(resource, name, links); 94 | defineProperty(resource, 'clientFor', function (relationship, clientOverride) { 95 | var link = links[relationship]; 96 | if (!link) { 97 | throw new Error('Unknown relationship: ' + relationship); 98 | } 99 | if (link.deprecation) { deprecationWarning(relationship, link.deprecation); } 100 | if (link.templated === true) { 101 | return template( 102 | clientOverride || client, 103 | { template: link.href } 104 | ); 105 | } 106 | return pathPrefix( 107 | clientOverride || client, 108 | { prefix: link.href } 109 | ); 110 | }); 111 | defineProperty(resource, 'requestFor', function (relationship, request, clientOverride) { 112 | var client = this.clientFor(relationship, clientOverride); 113 | return client(request); 114 | }); 115 | }); 116 | 117 | return root; 118 | }); 119 | 120 | }, 121 | 122 | write: function (obj, opts) { 123 | return opts.registry.lookup(opts.mime.suffix).then(function (converter) { 124 | return converter.write(obj, opts); 125 | }); 126 | } 127 | 128 | }; 129 | -------------------------------------------------------------------------------- /mime/type/application/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Create a new JSON converter with custom reviver/replacer. 12 | * 13 | * The extended converter must be published to a MIME registry in order 14 | * to be used. The existing converter will not be modified. 15 | * 16 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON 17 | * 18 | * @param {function} [reviver=undefined] custom JSON.parse reviver 19 | * @param {function|Array} [replacer=undefined] custom JSON.stringify replacer 20 | */ 21 | function createConverter(reviver, replacer) { 22 | return { 23 | 24 | read: function (str) { 25 | return JSON.parse(str, reviver); 26 | }, 27 | 28 | write: function (obj) { 29 | return JSON.stringify(obj, replacer); 30 | }, 31 | 32 | extend: createConverter 33 | 34 | }; 35 | } 36 | 37 | module.exports = createConverter(); 38 | -------------------------------------------------------------------------------- /mime/type/application/x-www-form-urlencoded.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var encodedSpaceRE, urlEncodedSpaceRE; 11 | 12 | encodedSpaceRE = /%20/g; 13 | urlEncodedSpaceRE = /\+/g; 14 | 15 | function urlEncode(str) { 16 | str = encodeURIComponent(str); 17 | // spec says space should be encoded as '+' 18 | return str.replace(encodedSpaceRE, '+'); 19 | } 20 | 21 | function urlDecode(str) { 22 | // spec says space should be encoded as '+' 23 | str = str.replace(urlEncodedSpaceRE, ' '); 24 | return decodeURIComponent(str); 25 | } 26 | 27 | function append(str, name, value) { 28 | if (Array.isArray(value)) { 29 | value.forEach(function (value) { 30 | str = append(str, name, value); 31 | }); 32 | } 33 | else { 34 | if (str.length > 0) { 35 | str += '&'; 36 | } 37 | str += urlEncode(name); 38 | if (value !== undefined && value !== null) { 39 | str += '=' + urlEncode(value); 40 | } 41 | } 42 | return str; 43 | } 44 | 45 | module.exports = { 46 | 47 | read: function (str) { 48 | var obj = {}; 49 | str.split('&').forEach(function (entry) { 50 | var pair, name, value; 51 | pair = entry.split('='); 52 | name = urlDecode(pair[0]); 53 | if (pair.length === 2) { 54 | value = urlDecode(pair[1]); 55 | } 56 | else { 57 | value = null; 58 | } 59 | if (name in obj) { 60 | if (!Array.isArray(obj[name])) { 61 | // convert to an array, perserving currnent value 62 | obj[name] = [obj[name]]; 63 | } 64 | obj[name].push(value); 65 | } 66 | else { 67 | obj[name] = value; 68 | } 69 | }); 70 | return obj; 71 | }, 72 | 73 | write: function (obj) { 74 | var str = ''; 75 | Object.keys(obj).forEach(function (name) { 76 | str = append(str, name, obj[name]); 77 | }); 78 | return str; 79 | } 80 | 81 | }; 82 | -------------------------------------------------------------------------------- /mime/type/multipart/form-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Michael Jackson 6 | */ 7 | 8 | /* global FormData, File, Blob */ 9 | 10 | 'use strict'; 11 | 12 | function isFormElement(object) { 13 | return object && 14 | object.nodeType === 1 && // Node.ELEMENT_NODE 15 | object.tagName === 'FORM'; 16 | } 17 | 18 | function createFormDataFromObject(object) { 19 | var formData = new FormData(); 20 | 21 | var value; 22 | for (var property in object) { 23 | if (object.hasOwnProperty(property)) { 24 | value = object[property]; 25 | 26 | if (value instanceof File) { 27 | formData.append(property, value, value.name); 28 | } else if (value instanceof Blob) { 29 | formData.append(property, value); 30 | } else { 31 | formData.append(property, String(value)); 32 | } 33 | } 34 | } 35 | 36 | return formData; 37 | } 38 | 39 | module.exports = { 40 | 41 | write: function (object) { 42 | if (typeof FormData === 'undefined') { 43 | throw new Error('The multipart/form-data mime serializer requires FormData support'); 44 | } 45 | 46 | // Support FormData directly. 47 | if (object instanceof FormData) { 48 | return object; 49 | } 50 | 51 | // Support
elements. 52 | if (isFormElement(object)) { 53 | return new FormData(object); 54 | } 55 | 56 | // Support plain objects, may contain File/Blob as value. 57 | if (typeof object === 'object' && object !== null) { 58 | return createFormDataFromObject(object); 59 | } 60 | 61 | throw new Error('Unable to create FormData from object ' + object); 62 | } 63 | 64 | }; 65 | -------------------------------------------------------------------------------- /mime/type/text/plain.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | module.exports = { 11 | 12 | read: function (str) { 13 | return str; 14 | }, 15 | 16 | write: function (obj) { 17 | return obj.toString(); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var rest = require('./client/default'), 11 | node = require('./client/node'); 12 | 13 | rest.setPlatformDefaultClient(node); 14 | 15 | module.exports = rest; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest", 3 | "version": "2.0.0", 4 | "description": "RESTful HTTP client library", 5 | "keywords": ["rest", "http", "client", "rest-template", "spring", "cujojs"], 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/cujojs/rest.git" 10 | }, 11 | "bugs": "https://github.com/cujojs/rest/issues", 12 | "maintainers": [ 13 | { 14 | "name": "Scott Andrews", 15 | "email": "scothis@gmail.com", 16 | "web": "http://twitter.com/scothis" 17 | } 18 | ], 19 | "contributors": [ 20 | { 21 | "name": "Jeremy Grelle", 22 | "email": "jeremy.grelle@gmail.com" 23 | }, 24 | { 25 | "name": "John Hann", 26 | "web": "http://unscriptable.com" 27 | }, 28 | { 29 | "name": "Michael Jackson", 30 | "web": "https://github.com/mjackson" 31 | } 32 | ], 33 | "dependencies": {}, 34 | "devDependencies": { 35 | "when": "~3", 36 | "wire": "~0.9", 37 | "test-support": "~0.4", 38 | "curl": "https://github.com/cujojs/curl/tarball/0.7.3", 39 | "poly": "https://github.com/cujojs/poly/tarball/0.5.1" 40 | }, 41 | "main": "./node", 42 | "browser": "./browser", 43 | "jspm": { 44 | "registry": "npm", 45 | "main": "./browser" 46 | }, 47 | "scripts": { 48 | "test": "npm run-script lint && npm run-script buster", 49 | "start": "buster static -e browser", 50 | "tunnel": "sauceme -m", 51 | "lint": "jshint .", 52 | "buster": "buster test --node", 53 | "sauceme": "sauceme" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /parsers/rfc5988.pegjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | start = 9 | start:(i:LinkValue OptionalSP ',' OptionalSP {return i;})* last:LinkValue 10 | { return start.concat([last]) } 11 | 12 | LinkValue = 13 | '<' href:URIReference '>' OptionalSP params:LinkParams* 14 | { 15 | var link = {}; 16 | params.forEach(function (param) { 17 | link[param[0]] = param[1]; 18 | }); 19 | link.href = href; 20 | return link; 21 | } 22 | 23 | LinkParams = 24 | ';' OptionalSP param:LinkParam OptionalSP 25 | { return param } 26 | 27 | URIReference = 28 | // TODO see http://tools.ietf.org/html/rfc3987#section-3.1 29 | url:[^>]+ 30 | { return url.join('') } 31 | 32 | LinkParam = 33 | name:LinkParamName value:LinkParamValue? 34 | { return [name, value] } 35 | 36 | LinkParamName = 37 | name:[a-z]+ 38 | { return name.join('') } 39 | 40 | LinkParamValue = 41 | "=" str:(PToken / QuotedString) 42 | { return str } 43 | 44 | PToken = 45 | token:PTokenChar+ 46 | { return token.join('') } 47 | 48 | PTokenChar = '!' / '#' / '$' / '%' / '&' / "'" / '(' 49 | / ')' / '*' / '+' / '-' / '.' / '|' / Digit 50 | / ':' / '<' / '=' / '>' / '?' / '@' / Alpha 51 | / '[' / ']' / '^' / '_' / '`' / '{' / [//] 52 | / '}' / '~' 53 | 54 | OptionalSP = 55 | SP* 56 | 57 | QuotedString = 58 | DQ str:QuotedStringInternal DQ 59 | { return str } 60 | 61 | QuotedStringInternal = 62 | str:(QDText / QuotedPair )* 63 | { return str.join('') } 64 | 65 | Char = [\x00-\x7F] 66 | UpAlpha = [A-Z] 67 | LoAlpha = [a-z] 68 | Alpha = UpAlpha / LoAlpha 69 | Digit = [0-9] 70 | SP = [\x20] 71 | DQ = [\x22] 72 | QDText = [^"] 73 | QuotedPair = [\\] Char 74 | -------------------------------------------------------------------------------- /rest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | if (console) { 11 | (console.warn || console.log).call(console, 'rest.js: The main module has moved, please switch your configuration to use \'rest/browser\' as the main module for browser applications.'); 12 | } 13 | 14 | module.exports = require('./browser'); 15 | -------------------------------------------------------------------------------- /test/browser-test-browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/browser-test', function (require) { 19 | 20 | var rest, defaultClient, xhr; 21 | 22 | rest = require('rest/browser'); 23 | defaultClient = require('rest/client/default'); 24 | xhr = require('rest/client/xhr'); 25 | 26 | buster.testCase('rest/browser', { 27 | 'should be the default client': function () { 28 | assert.same(defaultClient, rest); 29 | }, 30 | 'should wrap the xhr client': function () { 31 | assert.same(xhr, rest.getDefaultClient()); 32 | } 33 | }); 34 | 35 | }); 36 | 37 | }( 38 | this.buster || require('buster'), 39 | typeof define === 'function' && define.amd ? define : function (id, factory) { 40 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 41 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 42 | factory(function (moduleId) { 43 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 44 | }); 45 | } 46 | // Boilerplate for AMD and Node 47 | )); 48 | -------------------------------------------------------------------------------- /test/browsers/android-4.3.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'android', version: '4.3', platform: 'Linux' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/android-4.4.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'android', version: '4.4', platform: 'Linux' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/android-5.1.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'android', version: '5.1', platform: 'Linux' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/chrome.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'chrome', platform: 'Windows 2008' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/edge.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'MicrosoftEdge', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/firefox-38.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'firefox', version: '38', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/firefox-45.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'firefox', version: '45', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/firefox.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'firefox', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/ie-11.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'internet explorer', version: '11', platform: 'Windows 8.1' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/ios-8.4.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'ipad', version: '8.4', platform: 'OS X 10.10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/ios-9.2.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'ipad', version: '9.2', platform: 'Mac 10.10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/safari-8.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'safari', version: '8', platform: 'Mac 10.10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/safari-9.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'safari', version: '9', platform: 'Mac 10.11' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/buster.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | var config = exports; 9 | 10 | if (typeof Promise === 'undefined') { 11 | require('when/es6-shim/Promise'); 12 | } 13 | 14 | config['rest:node'] = { 15 | environment: 'node', 16 | rootPath: '../', 17 | tests: [ 18 | 'test/**/*-test.js', 19 | 'test/**/*-test-node.js' 20 | ], 21 | testHelpers: ['test/failOnThrow.js'] 22 | }; 23 | 24 | config['rest:browser'] = { 25 | environment: 'browser', 26 | autoRun: false, 27 | rootPath: '../', 28 | resources: [ 29 | //'**', ** is busted in buster 30 | '*.js', 31 | 'client/**/*.js', 32 | 'interceptor/**/*.js', 33 | 'mime/**/*.js', 34 | 'parsers/**/*.js', 35 | 'util/**/*.js', 36 | 'node_modules/curl/**/*.js', 37 | 'node_modules/poly/**/*.js', 38 | 'node_modules/when/**/*.js', 39 | 'node_modules/wire/**/*.js', 40 | 'test/**/fixtures/**', 41 | { path: '/wait', backend: 'http://example.com' } 42 | ], 43 | libs: [ 44 | 'node_modules/when/es6-shim/Promise.js', 45 | 'test/curl-config.js', 46 | 'node_modules/curl/src/curl.js' 47 | ], 48 | sources: [ 49 | // loaded as resources 50 | ], 51 | tests: [ 52 | 'test/**/*-test.js', 53 | 'test/**/*-test-browser.js', 54 | 'test/run.js' 55 | ], 56 | testHelpers: ['test/failOnThrow.js'] 57 | }; 58 | -------------------------------------------------------------------------------- /test/client-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/client-test', function (require) { 19 | 20 | var client, rest, interceptor, defaultClient, skippableClient, defaultInterceptor; 21 | 22 | client = require('rest/client'); 23 | rest = require('rest/client/default'); 24 | interceptor = require('rest/interceptor'); 25 | 26 | buster.testCase('rest/client', { 27 | 'setUp': function () { 28 | defaultClient = client(function (request) { 29 | return { request: request, id: 'default' }; 30 | }); 31 | skippableClient = client(function (request) { 32 | return { request: request, id: 'default' }; 33 | }, rest); 34 | defaultInterceptor = interceptor(); 35 | }, 36 | 37 | 'should wrap the client with an interceptor': function () { 38 | assert(typeof defaultClient.wrap(defaultInterceptor) === 'function'); 39 | }, 40 | 'should continue to support chain as a alias for wrap': function () { 41 | var config = {}; 42 | this.spy(defaultClient, 'wrap'); 43 | defaultClient.chain(defaultInterceptor, config); 44 | assert.calledWith(defaultClient.wrap, defaultInterceptor, config); 45 | defaultClient.wrap.restore(); 46 | }, 47 | 'should return the next client in the chain': function () { 48 | assert.same(rest, skippableClient.skip()); 49 | refute(defaultClient.skip); 50 | } 51 | }); 52 | 53 | }); 54 | 55 | }( 56 | this.buster || require('buster'), 57 | typeof define === 'function' && define.amd ? define : function (id, factory) { 58 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 59 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 60 | factory(function (moduleId) { 61 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 62 | }); 63 | } 64 | // Boilerplate for AMD and Node 65 | )); 66 | -------------------------------------------------------------------------------- /test/client/fixtures/data.js: -------------------------------------------------------------------------------- 1 | callback({ data: true }); 2 | -------------------------------------------------------------------------------- /test/client/fixtures/noop.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cujojs/rest/03107bf6f369ae3667287f2211576a38b0ce2afd/test/client/fixtures/noop.js -------------------------------------------------------------------------------- /test/client/fixtures/throw.js: -------------------------------------------------------------------------------- 1 | notcallback({ data: true }); -------------------------------------------------------------------------------- /test/client/jsonp-test-browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/client/jsonp-test', function (require) { 19 | 20 | var client, jsonpInterceptor, rest; 21 | 22 | client = require('rest/client/jsonp'); 23 | jsonpInterceptor = require('rest/interceptor/jsonp'); 24 | rest = require('rest'); 25 | 26 | buster.testCase('rest/client/jsonp', { 27 | 'should make a cross origin request': function () { 28 | var request = { path: 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=jsonp' }; 29 | return client(request).then(function (response) { 30 | assert.match(response.url, 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=jsonp&callback='); 31 | assert(Object.keys(response.entity).length); 32 | assert.same(request, response.request); 33 | refute(request.canceled); 34 | refute(response.raw.parentNode); 35 | })['catch'](fail); 36 | }, 37 | 'should use the jsonp client from the jsonp interceptor by default': function () { 38 | var request = { path: '/test/client/fixtures/data.js', callback: { name: 'callback' } }; 39 | return jsonpInterceptor()(request).then(function (response) { 40 | assert.match(response.url, '/test/client/fixtures/data.js?callback=callback'); 41 | assert(response.entity.data); 42 | assert.same(request, response.request); 43 | refute(request.canceled); 44 | refute(response.raw.parentNode); 45 | })['catch'](fail); 46 | }, 47 | 'should abort the request if canceled': function () { 48 | var request, response; 49 | request = { path: 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=jsonp' }; 50 | response = client(request).then( 51 | fail, 52 | failOnThrow(function (response) { 53 | assert.match(response.url, 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=jsonp&callback='); 54 | assert.same(request, response.request); 55 | assert(request.canceled); 56 | refute(response.raw.parentNode); 57 | }) 58 | ); 59 | refute(request.canceled); 60 | request.cancel(); 61 | return response; 62 | }, 63 | 'should propogate request errors': function () { 64 | var request = { path: 'http://localhost:1234' }; 65 | return client(request).then( 66 | fail, 67 | failOnThrow(function (response) { 68 | assert.match(response.url, 'http://localhost:1234?callback='); 69 | assert.same('loaderror', response.error); 70 | }) 71 | ); 72 | }, 73 | 'should not make a request that has already been canceled': function () { 74 | var request = { canceled: true, path: 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=html5' }; 75 | return client(request).then( 76 | fail, 77 | failOnThrow(function (response) { 78 | assert.same(request, response.request); 79 | assert(request.canceled); 80 | assert.same('precanceled', response.error); 81 | }) 82 | ); 83 | }, 84 | 'should error if callback not invoked': function () { 85 | var request = { path: '/test/client/fixtures/noop.js' }; 86 | return client(request).then( 87 | fail, 88 | failOnThrow(function (response) { 89 | assert.match(response.url, '/test/client/fixtures/noop.js?callback='); 90 | assert.same('loaderror', response.error); 91 | }) 92 | ); 93 | }, 94 | 'should error if script throws': function () { 95 | var request = { path: '/test/client/fixtures/throw.js' }; 96 | return client(request).then( 97 | fail, 98 | failOnThrow(function (response) { 99 | assert.match(response.url, '/test/client/fixtures/throw.js?callback='); 100 | assert.same('loaderror', response.error); 101 | }) 102 | ); 103 | }, 104 | 'should normalize a string to a request object': function () { 105 | var request = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=jsonp'; 106 | return client(request).then(function (response) { 107 | assert.match(response.url, 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=jsonp&callback='); 108 | assert.same(request, response.request.path); 109 | })['catch'](fail); 110 | }, 111 | 'should not be the default client': function () { 112 | rest.resetDefaultClient(); 113 | refute.same(client, rest.getDefaultClient()); 114 | }, 115 | 'should support interceptor wrapping': function () { 116 | assert(typeof client.wrap === 'function'); 117 | }, 118 | 'should return a ResponsePromise': function () { 119 | var response = client(); 120 | response['catch'](function () {}); 121 | assert.isFunction(response.entity); 122 | } 123 | }); 124 | 125 | }); 126 | 127 | }( 128 | this.buster || require('buster'), 129 | typeof define === 'function' && define.amd ? define : function (id, factory) { 130 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 131 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 132 | factory(function (moduleId) { 133 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 134 | }); 135 | } 136 | // Boilerplate for AMD and Node 137 | )); 138 | -------------------------------------------------------------------------------- /test/client/node-ssl.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB/zCCAWgCCQDvBizVJkxhzDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoT 4 | BmN1am9KUzAgFw0xNDA2MjQxNjM4MTBaGA8yMTE0MDUzMTE2MzgxMFowQzELMAkG 5 | A1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMQ8w 6 | DQYDVQQKEwZjdWpvSlMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMVyyv6a 7 | I2Hqe4wa+Ae9Iq1ExphrAvw+9oLBZezGzjtk+94CwcBTtDgyEqrDF6uQreKvyR3G 8 | 51ss4/boo9SZg9tPfg0bzIQZq6usK/zL+tyTcGWD7YyV00lCJPsKaNavLEwFv0Ak 9 | /tROBxc/kiUTKVj9rydmlEsj76llId8rONW7AgMBAAEwDQYJKoZIhvcNAQEFBQAD 10 | gYEAInY9rNwVS4fQza1wt+dGSAboss6sbR9cwJSYe/UcNI6C2mKAcYhpjbnJtSaM 11 | PtLGHwdCB5gdqkyDg91vr+SmJBslso+yDtfvTxrs5kzcMoM0ussMsc2OEbW+MzRX 12 | EL+hinrTYPOPIhgHn3UZtnNzTf3fa1VwLVSmWvWM1jzEmSM= 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /test/client/node-ssl.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDFcsr+miNh6nuMGvgHvSKtRMaYawL8PvaCwWXsxs47ZPveAsHA 3 | U7Q4MhKqwxerkK3ir8kdxudbLOP26KPUmYPbT34NG8yEGaurrCv8y/rck3Blg+2M 4 | ldNJQiT7CmjWryxMBb9AJP7UTgcXP5IlEylY/a8nZpRLI++pZSHfKzjVuwIDAQAB 5 | AoGBAKH9tB1+SFklD4e6+JMg0Tpmmqih4ykgXw6XrQ+9a6W/DATxLhSkl88jBb2j 6 | od/YK1E390C+10Euazk/VGowKMRsDyyGAnnbo2BdSo1iT+TVDT3wn3yboXgc4hsj 7 | QWiLfSMKkCepFnUhTX8UDRAQXiNzs9GpQo9gNmOQPnaEM0eBAkEA6neMmy6okW/s 8 | +OWtLEYdhcQt8rx3G3sdEIWaHaw+Qa7j1fbRPniqF3pLSILE78r1pO+b0AhrFzhw 9 | rKDB4R2YEwJBANeU6oTjLmLN86RA47sku9SRC4H32TSS0BAhm1Qg1D47VB1oltm8 10 | JXP52Gm6YMxEM81YB8YjZHlR2OTUuhdwULkCQA+hcqUxwhkX/hNFtHq8HeG6B1ok 11 | SEhzt6dPUMvjnK7iStwLvT1N7ADurTAjT5+wxxl8w8VlmkYNBC3t9Z7dSzMCQCEF 12 | CB6uhU5Q6T6BSeBCMhrO1Iplofkxe3jdDOOH27pkb+/JM0HKVZo77G/VQjpHP//1 13 | ucsZZKxIYwY5pv8sDtECQFnAJ8JU97icTib81EiaaXDctngLtTo//z7E73sFKyId 14 | FBr+0OCsHcWq4j94BXRZKdc7g4qlQXe6+onL4+uW8ok= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/curl-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | window.curl = { 11 | packages: [ 12 | { name: 'rest', location: './', main: 'browser', config: { moduleLoader: 'curl/loader/cjsm11' } }, 13 | { name: 'curl', location: 'node_modules/curl/src/curl', main: 'curl' }, 14 | { name: 'poly', location: 'node_modules/poly', main: 'poly' }, 15 | { name: 'when', location: 'node_modules/when', main: 'when' }, 16 | { name: 'wire', location: 'node_modules/wire', main: 'wire' } 17 | ], 18 | // avoid poly/xhr as we need to test the case without it 19 | preloads: ['poly/object', 'poly/string', 'poly/date', 'poly/array', 'poly/function', 'poly/json'] 20 | }; 21 | -------------------------------------------------------------------------------- /test/failOnThrow.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster) { 9 | 'use strict'; 10 | 11 | var fail; 12 | 13 | fail = buster.assertions.fail; 14 | 15 | buster.assertions.failOnThrow = function failOnThrow(func) { 16 | return function () { 17 | try { 18 | return func.apply(this, arguments); 19 | } 20 | catch (e) { 21 | fail(e); 22 | } 23 | }; 24 | }; 25 | 26 | }( 27 | this.buster || require('buster') 28 | )); 29 | -------------------------------------------------------------------------------- /test/interceptor/basicAuth-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/basicAuth-test', function (require) { 18 | 19 | var basicAuth, rest; 20 | 21 | basicAuth = require('rest/interceptor/basicAuth'); 22 | rest = require('rest'); 23 | 24 | buster.testCase('rest/interceptor/basicAuth', { 25 | 'should authenticate the requst from the config': function () { 26 | var client = basicAuth( 27 | function (request) { return { request: request }; }, 28 | { username: 'user', password: 'pass'} 29 | ); 30 | return client({}).then(function (response) { 31 | assert.equals('Basic dXNlcjpwYXNz', response.request.headers.Authorization); 32 | })['catch'](fail); 33 | }, 34 | 'should authenticate the requst from the request': function () { 35 | var client = basicAuth( 36 | function (request) { return { request: request }; } 37 | ); 38 | return client({ username: 'user', password: 'pass'}).then(function (response) { 39 | assert.equals('Basic dXNlcjpwYXNz', response.request.headers.Authorization); 40 | })['catch'](fail); 41 | }, 42 | 'should not authenticate without a username': function () { 43 | var client = basicAuth( 44 | function (request) { return { request: request }; } 45 | ); 46 | return client({}).then(function (response) { 47 | refute.defined(response.request.headers.Authorization); 48 | })['catch'](fail); 49 | }, 50 | 'should have the default client as the parent by default': function () { 51 | assert.same(rest, basicAuth().skip()); 52 | }, 53 | 'should support interceptor wrapping': function () { 54 | assert(typeof basicAuth().wrap === 'function'); 55 | } 56 | }); 57 | 58 | }); 59 | 60 | }( 61 | this.buster || require('buster'), 62 | typeof define === 'function' && define.amd ? define : function (id, factory) { 63 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 64 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 65 | factory(function (moduleId) { 66 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 67 | }); 68 | } 69 | // Boilerplate for AMD and Node 70 | )); 71 | -------------------------------------------------------------------------------- /test/interceptor/csrf-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/csrf-test', function (require) { 18 | 19 | var csrf, rest; 20 | 21 | csrf = require('rest/interceptor/csrf'); 22 | rest = require('rest'); 23 | 24 | buster.testCase('rest/interceptor/csrf', { 25 | 'should protect the requst from the config': function () { 26 | var client = csrf( 27 | function (request) { return { request: request }; }, 28 | { token: 'abc123xyz789'} 29 | ); 30 | return client({}).then(function (response) { 31 | assert.equals('abc123xyz789', response.request.headers['X-Csrf-Token']); 32 | })['catch'](fail); 33 | }, 34 | 'should protect the requst from the request': function () { 35 | var client = csrf( 36 | function (request) { return { request: request }; } 37 | ); 38 | return client({ csrfToken: 'abc123xyz789' }).then(function (response) { 39 | assert.equals('abc123xyz789', response.request.headers['X-Csrf-Token']); 40 | })['catch'](fail); 41 | }, 42 | 'should protect the requst from the config using a custom header': function () { 43 | var client = csrf( 44 | function (request) { return { request: request }; }, 45 | { token: 'abc123xyz789', name: 'Csrf-Token' } 46 | ); 47 | return client({}).then(function (response) { 48 | assert.equals('abc123xyz789', response.request.headers['Csrf-Token']); 49 | })['catch'](fail); 50 | }, 51 | 'should protect the requst from the request using a custom header': function () { 52 | var client = csrf( 53 | function (request) { return { request: request }; } 54 | ); 55 | return client({ csrfToken: 'abc123xyz789', csrfTokenName: 'Csrf-Token' }).then(function (response) { 56 | assert.equals('abc123xyz789', response.request.headers['Csrf-Token']); 57 | })['catch'](fail); 58 | }, 59 | 'should not protect without a token': function () { 60 | var client = csrf( 61 | function (request) { return { request: request }; } 62 | ); 63 | return client({}).then(function (response) { 64 | refute.defined(response.request.headers['X-Csrf-Token']); 65 | })['catch'](fail); 66 | }, 67 | 'should have the default client as the parent by default': function () { 68 | assert.same(rest, csrf().skip()); 69 | }, 70 | 'should support interceptor wrapping': function () { 71 | assert(typeof csrf().wrap === 'function'); 72 | } 73 | }); 74 | 75 | }); 76 | 77 | }( 78 | this.buster || require('buster'), 79 | typeof define === 'function' && define.amd ? define : function (id, factory) { 80 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 81 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 82 | factory(function (moduleId) { 83 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 84 | }); 85 | } 86 | // Boilerplate for AMD and Node 87 | )); 88 | -------------------------------------------------------------------------------- /test/interceptor/defaultRequest-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/defaultRequest-test', function (require) { 18 | 19 | var defaultRequest, rest; 20 | 21 | defaultRequest = require('rest/interceptor/defaultRequest'); 22 | rest = require('rest'); 23 | 24 | function defaultClient(request) { 25 | return { request: request }; 26 | } 27 | 28 | buster.testCase('rest/interceptor/defaultRequest', { 29 | 'should do nothing by default': function () { 30 | var client = defaultRequest(defaultClient); 31 | return client({}).then(function (response) { 32 | assert.same(client, response.request.originator); 33 | delete response.request.originator; 34 | assert.equals({}, response.request); 35 | })['catch'](fail); 36 | }, 37 | 'should default the method': function () { 38 | var client = defaultRequest(defaultClient, { method: 'PUT' }); 39 | return client({}).then(function (response) { 40 | assert.equals('PUT', response.request.method); 41 | })['catch'](fail); 42 | }, 43 | 'should not overwrite the method': function () { 44 | var client = defaultRequest(defaultClient, { method: 'PUT' }); 45 | return client({ method: 'GET' }).then(function (response) { 46 | assert.equals('GET', response.request.method); 47 | })['catch'](fail); 48 | }, 49 | 'should default the path': function () { 50 | var client = defaultRequest(defaultClient, { path: '/foo' }); 51 | return client({}).then(function (response) { 52 | assert.equals('/foo', response.request.path); 53 | })['catch'](fail); 54 | }, 55 | 'should not overwrite the path': function () { 56 | var client = defaultRequest(defaultClient, { path: '/foo' }); 57 | return client({ path: '/bar' }).then(function (response) { 58 | assert.equals('/bar', response.request.path); 59 | })['catch'](fail); 60 | }, 61 | 'should default params': function () { 62 | var client = defaultRequest(defaultClient, { params: { foo: 'bar', bool: 'false' } }); 63 | return client({}).then(function (response) { 64 | assert.equals('bar', response.request.params.foo); 65 | assert.equals('false', response.request.params.bool); 66 | })['catch'](fail); 67 | }, 68 | 'should merge params': function () { 69 | var client = defaultRequest(defaultClient, { params: { foo: 'bar', bool: 'false' } }); 70 | return client({ params: { bool: 'true', bleep: 'bloop' } }).then(function (response) { 71 | assert.equals('bar', response.request.params.foo); 72 | assert.equals('true', response.request.params.bool); 73 | assert.equals('bloop', response.request.params.bleep); 74 | })['catch'](fail); 75 | }, 76 | 'should default headers': function () { 77 | var client = defaultRequest(defaultClient, { headers: { foo: 'bar', bool: 'false' } }); 78 | return client({}).then(function (response) { 79 | assert.equals('bar', response.request.headers.foo); 80 | assert.equals('false', response.request.headers.bool); 81 | })['catch'](fail); 82 | }, 83 | 'should merge headers': function () { 84 | var client = defaultRequest(defaultClient, { headers: { foo: 'bar', bool: 'false' } }); 85 | return client({ headers: { bool: 'true', bleep: 'bloop' } }).then(function (response) { 86 | assert.equals('bar', response.request.headers.foo); 87 | assert.equals('true', response.request.headers.bool); 88 | assert.equals('bloop', response.request.headers.bleep); 89 | })['catch'](fail); 90 | }, 91 | 'should default the entity': function () { 92 | var client = defaultRequest(defaultClient, { entity: Math }); 93 | return client({}).then(function (response) { 94 | assert.same(Math, response.request.entity); 95 | })['catch'](fail); 96 | }, 97 | 'should not overwrite the entity': function () { 98 | var client = defaultRequest(defaultClient, { entity: Math }); 99 | return client({ entity: Date }).then(function (response) { 100 | assert.same(Date, response.request.entity); 101 | })['catch'](fail); 102 | }, 103 | 'should have the default client as the parent by default': function () { 104 | assert.same(rest, defaultRequest().skip()); 105 | }, 106 | 'should support interceptor wrapping': function () { 107 | assert(typeof defaultRequest().wrap === 'function'); 108 | } 109 | }); 110 | 111 | }); 112 | 113 | }( 114 | this.buster || require('buster'), 115 | typeof define === 'function' && define.amd ? define : function (id, factory) { 116 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 117 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 118 | factory(function (moduleId) { 119 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 120 | }); 121 | } 122 | // Boilerplate for AMD and Node 123 | )); 124 | -------------------------------------------------------------------------------- /test/interceptor/entity-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/entity-test', function (require) { 18 | 19 | var entity, rest; 20 | 21 | entity = require('rest/interceptor/entity'); 22 | rest = require('rest'); 23 | 24 | buster.testCase('rest/interceptor/entity', { 25 | 'should return the response entity': function () { 26 | var client, body; 27 | 28 | body = {}; 29 | client = entity(function () { return { entity: body }; }); 30 | 31 | return client().then(function (response) { 32 | assert.same(body, response); 33 | })['catch'](fail); 34 | }, 35 | 'should return the whole response if there is no entity': function () { 36 | var client, response; 37 | 38 | response = {}; 39 | client = entity(function () { return response; }); 40 | 41 | return client().then(function (r) { 42 | assert.same(response, r); 43 | })['catch'](fail); 44 | }, 45 | 'should have the default client as the parent by default': function () { 46 | assert.same(rest, entity().skip()); 47 | }, 48 | 'should support interceptor wrapping': function () { 49 | assert(typeof entity().wrap === 'function'); 50 | } 51 | }); 52 | 53 | }); 54 | 55 | }( 56 | this.buster || require('buster'), 57 | typeof define === 'function' && define.amd ? define : function (id, factory) { 58 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 59 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 60 | factory(function (moduleId) { 61 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 62 | }); 63 | } 64 | // Boilerplate for AMD and Node 65 | )); 66 | -------------------------------------------------------------------------------- /test/interceptor/errorCode-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/interceptor/errorCode-test', function (require) { 19 | 20 | var errorCode, rest; 21 | 22 | errorCode = require('rest/interceptor/errorCode'); 23 | rest = require('rest'); 24 | 25 | buster.testCase('rest/interceptor/errorCode', { 26 | 'should resolve for less than 400 by default': function () { 27 | var client = errorCode( 28 | function () { return { status: { code: 399 } }; } 29 | ); 30 | return client({}).then(function (response) { 31 | assert.equals(399, response.status.code); 32 | })['catch'](fail); 33 | }, 34 | 'should reject for 400 or greater by default': function () { 35 | var client = errorCode( 36 | function () { return { status: { code: 400 } }; } 37 | ); 38 | return client({}).then( 39 | fail, 40 | failOnThrow(function (response) { 41 | assert.equals(400, response.status.code); 42 | }) 43 | ); 44 | }, 45 | 'should reject lower then 400 with a custom code': function () { 46 | var client = errorCode( 47 | function () { return { status: { code: 300 } }; }, 48 | { code: 300 } 49 | ); 50 | return client({}).then( 51 | fail, 52 | failOnThrow(function (response) { 53 | assert.equals(300, response.status.code); 54 | }) 55 | ); 56 | }, 57 | 'should have the default client as the parent by default': function () { 58 | assert.same(rest, errorCode().skip()); 59 | }, 60 | 'should support interceptor warpping': function () { 61 | assert(typeof errorCode().wrap === 'function'); 62 | } 63 | }); 64 | 65 | }); 66 | 67 | }( 68 | this.buster || require('buster'), 69 | typeof define === 'function' && define.amd ? define : function (id, factory) { 70 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 71 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 72 | factory(function (moduleId) { 73 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 74 | }); 75 | } 76 | // Boilerplate for AMD and Node 77 | )); 78 | -------------------------------------------------------------------------------- /test/interceptor/jsonp-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/jsonp-test', function (require) { 18 | 19 | var jsonp, rest, jsonpClient, when; 20 | 21 | jsonp = require('rest/interceptor/jsonp'); 22 | jsonpClient = require('rest/client/jsonp'); 23 | rest = require('rest'); 24 | when = require('when'); 25 | 26 | buster.testCase('rest/interceptor/jsonp', { 27 | 'should include callback info from config in request by default': function () { 28 | var client = jsonp( 29 | function (request) { return when({ request: request }); }, 30 | { callback: { param: 'callback', prefix: 'jsonp', name: 'jsonp123456' } } 31 | ); 32 | return client({}).then(function (response) { 33 | assert.equals('callback', response.request.callback.param); 34 | assert.equals('jsonp', response.request.callback.prefix); 35 | assert.equals('jsonp123456', response.request.callback.name); 36 | })['catch'](fail); 37 | }, 38 | 'should include callback info from request overridding config values': function () { 39 | var client = jsonp( 40 | function (request) { return when({ request: request }); }, 41 | { callback: { param: 'callback', prefix: 'jsonp', name: 'jsonp123456' } } 42 | ); 43 | return client({ callback: { param: 'customCallback', prefix: 'customPrefix', name: 'customName' } }).then(function (response) { 44 | assert.equals('customCallback', response.request.callback.param); 45 | assert.equals('customPrefix', response.request.callback.prefix); 46 | assert.equals('customName', response.request.callback.name); 47 | })['catch'](fail); 48 | }, 49 | 'should have the jsonp client as the parent by default': function () { 50 | refute.same(rest, jsonp().skip()); 51 | assert.same(jsonpClient, jsonp().skip()); 52 | }, 53 | 'should support interceptor wrapping': function () { 54 | assert(typeof jsonp().wrap === 'function'); 55 | } 56 | }); 57 | 58 | }); 59 | 60 | }( 61 | this.buster || require('buster'), 62 | typeof define === 'function' && define.amd ? define : function (id, factory) { 63 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 64 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 65 | factory(function (moduleId) { 66 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 67 | }); 68 | } 69 | // Boilerplate for AMD and Node 70 | )); 71 | -------------------------------------------------------------------------------- /test/interceptor/location-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/location-test', function (require) { 18 | 19 | var location, rest; 20 | 21 | location = require('rest/interceptor/location'); 22 | rest = require('rest'); 23 | 24 | buster.testCase('rest/interceptor/location', { 25 | 'should follow the location header': function () { 26 | var client, spy; 27 | spy = this.spy(function (request) { 28 | var response = { request: request, headers: { } }; 29 | if (spy.callCount < 3) { 30 | response.headers.Location = '/foo/' + spy.callCount; 31 | } 32 | return response; 33 | }); 34 | client = location(spy); 35 | return client({}).then(function (response) { 36 | refute(response.headers.Location); 37 | assert.same(3, spy.callCount); 38 | assert.same(spy.returnValues[0].headers.Location, '/foo/1'); 39 | assert.same(spy.args[1][0].path, '/foo/1'); 40 | assert.same(spy.args[1][0].method, 'GET'); 41 | assert.same(spy.returnValues[1].headers.Location, '/foo/2'); 42 | assert.same(spy.args[2][0].path, '/foo/2'); 43 | assert.same(spy.args[2][0].method, 'GET'); 44 | refute(spy.returnValues[2].headers.Location); 45 | })['catch'](fail); 46 | }, 47 | 'should follow the location header when status code is greater or equal to configured status code': function () { 48 | var client, spy; 49 | spy = this.spy(function (request) { 50 | var statusCode = 300; 51 | var response = { 52 | request: request, 53 | headers: { }, 54 | status: { code: statusCode } 55 | }; 56 | if (spy.callCount === 1) { 57 | response.headers.Location = '/foo'; 58 | } 59 | statusCode = statusCode - 1; 60 | return response; 61 | }); 62 | client = location(spy, { code: 300 }); 63 | return client({}).then(function () { 64 | assert.same(2, spy.callCount); 65 | assert.same(spy.args[1][0].path, '/foo'); 66 | })['catch'](fail); 67 | }, 68 | 'should return the response if there is no location header': function () { 69 | var client, spy; 70 | spy = this.spy(function () { return { status: { code: 200 } }; }); 71 | client = location(spy); 72 | return client({}).then(function (response) { 73 | assert.equals(200, response.status.code); 74 | assert.same(1, spy.callCount); 75 | })['catch'](fail); 76 | }, 77 | 'should have the default client as the parent by default': function () { 78 | assert.same(rest, location().skip()); 79 | }, 80 | 'should support interceptor wrapping': function () { 81 | assert(typeof location().wrap === 'function'); 82 | } 83 | }); 84 | 85 | }); 86 | 87 | }( 88 | this.buster || require('buster'), 89 | typeof define === 'function' && define.amd ? define : function (id, factory) { 90 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 91 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 92 | factory(function (moduleId) { 93 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 94 | }); 95 | } 96 | // Boilerplate for AMD and Node 97 | )); 98 | -------------------------------------------------------------------------------- /test/interceptor/oAuth-test-browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define, global) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/oAuth-test', function (require) { 18 | 19 | var oAuth, rest, pubsub; 20 | 21 | oAuth = require('rest/interceptor/oAuth'); 22 | pubsub = require('rest/util/pubsub'); 23 | rest = require('rest'); 24 | 25 | buster.testCase('rest/interceptor/oAuth', { 26 | 'should authenticate the request for a known token': function () { 27 | var client; 28 | 29 | client = oAuth( 30 | function (request) { return { request: request, status: { code: 200 } }; }, 31 | { token: 'bearer abcxyz' } 32 | ); 33 | 34 | return client({}).then(function (response) { 35 | assert.equals('bearer abcxyz', response.request.headers.Authorization); 36 | })['catch'](fail); 37 | }, 38 | 'should use implicit flow to authenticate the request': function () { 39 | var client, windowStrategy, windowStrategyClose, oAuthCallbackName; 40 | 41 | oAuthCallbackName = 'oAuthCallback' + Math.round(Math.random() * 100000); 42 | windowStrategyClose = this.spy(function () {}); 43 | windowStrategy = function (url) { 44 | var state; 45 | assert(url.indexOf('https://www.example.com/auth?response_type=token&redirect_uri=http%3A%2F%2Flocalhost%2FimplicitHandler&client_id=user&scope=openid&state=') === 0); 46 | state = url.substring(url.lastIndexOf('=') + 1); 47 | setTimeout(function () { 48 | global[oAuthCallbackName]('#state=' + state + '&=token_type=bearer&access_token=abcxyz'); 49 | }, 10); 50 | return windowStrategyClose; 51 | }; 52 | 53 | client = oAuth( 54 | function (request) { 55 | return { request: request, status: { code: 200 } }; 56 | }, 57 | { 58 | clientId: 'user', 59 | authorizationUrlBase: 'https://www.example.com/auth', 60 | redirectUrl: 'http://localhost/implicitHandler', 61 | scope: 'openid', 62 | windowStrategy: windowStrategy, 63 | oAuthCallbackName: oAuthCallbackName 64 | } 65 | ); 66 | 67 | return client({}).then(function (response) { 68 | assert.equals('bearer abcxyz', response.request.headers.Authorization); 69 | assert.called(windowStrategyClose); 70 | })['catch'](fail); 71 | }, 72 | 'should have the default client as the parent by default': function () { 73 | assert.same(rest, oAuth({ token: 'bearer abcxyz' }).skip()); 74 | }, 75 | 'should support interceptor wrapping': function () { 76 | assert(typeof oAuth().wrap === 'function'); 77 | } 78 | }); 79 | 80 | }); 81 | 82 | }( 83 | this.buster || require('buster'), 84 | typeof define === 'function' && define.amd ? define : function (id, factory) { 85 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 86 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 87 | factory(function (moduleId) { 88 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 89 | }); 90 | }, 91 | typeof global === 'undefined' ? this : global 92 | // Boilerplate for AMD and Node 93 | )); 94 | -------------------------------------------------------------------------------- /test/interceptor/params-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/params-test', function (require) { 18 | 19 | var params, rest; 20 | 21 | params = require('rest/interceptor/params'); 22 | rest = require('rest'); 23 | 24 | function parent(request) { 25 | return { request: request }; 26 | } 27 | 28 | buster.testCase('rest/interceptor/params', { 29 | 'should apply the params to the path': function () { 30 | var client, config; 31 | 32 | config = {}; 33 | client = params(parent, config); 34 | 35 | return client({ path: '/dictionary/{lang}/{term}', params: { term: 'hypermedia', lang: 'en-us' } }).then(function (response) { 36 | assert.same('/dictionary/en-us/hypermedia', response.request.path); 37 | refute('params' in response.request); 38 | })['catch'](fail); 39 | }, 40 | 'should apply the params from the config if not defined on the request': function () { 41 | var client, config; 42 | 43 | config = { params: { lang: 'en-us' } }; 44 | client = params(parent, config); 45 | 46 | return client({ path: '/dictionary/{lang}/{term}', params: { term: 'hypermedia' } }).then(function (response) { 47 | assert.same('/dictionary/en-us/hypermedia', response.request.path); 48 | refute('params' in response.request); 49 | })['catch'](fail); 50 | }, 51 | 'should apply the query params before config params': function () { 52 | var client, config; 53 | 54 | config = { params: { lang: 'en-us', term: 'invisible' } }; 55 | client = params(parent, config); 56 | 57 | return client({ path: '/dictionary/{lang}/{term}', params: { term: 'hypermedia' } }).then(function (response) { 58 | assert.same('/dictionary/en-us/hypermedia', response.request.path); 59 | refute('params' in response.request); 60 | })['catch'](fail); 61 | }, 62 | 'should apply overdefined params to the query string': function () { 63 | var client, config; 64 | 65 | config = {}; 66 | client = params(parent, config); 67 | 68 | return client({ path: '/dictionary', params: { q: 'hypermedia' } }).then(function (response) { 69 | assert.same('/dictionary?q=hypermedia', response.request.path); 70 | refute('params' in response.request); 71 | })['catch'](fail); 72 | }, 73 | 'should have the default client as the parent by default': function () { 74 | assert.same(rest, params().skip()); 75 | }, 76 | 'should support interceptor wrapping': function () { 77 | assert(typeof params().wrap === 'function'); 78 | } 79 | }); 80 | 81 | }); 82 | 83 | }( 84 | this.buster || require('buster'), 85 | typeof define === 'function' && define.amd ? define : function (id, factory) { 86 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 87 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 88 | factory(function (moduleId) { 89 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 90 | }); 91 | } 92 | // Boilerplate for AMD and Node 93 | )); 94 | -------------------------------------------------------------------------------- /test/interceptor/pathPrefix-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/pathPrefix-test', function (require) { 18 | 19 | var pathPrefix, rest; 20 | 21 | pathPrefix = require('rest/interceptor/pathPrefix'); 22 | rest = require('rest'); 23 | 24 | buster.testCase('rest/interceptor/pathPrefix', { 25 | 'should prepend prefix before path': function () { 26 | var client = pathPrefix( 27 | function (request) { return { request: request }; }, 28 | { prefix: '/foo' } 29 | ); 30 | return client({ path: '/bar' }).then(function (response) { 31 | assert.equals('/foo/bar', response.request.path); 32 | })['catch'](fail); 33 | }, 34 | 'should prepend prefix before path, adding slash between path segments': function () { 35 | var client = pathPrefix( 36 | function (request) { return { request: request }; }, 37 | { prefix: '/foo' } 38 | ); 39 | return client({ path: 'bar' }).then(function (response) { 40 | assert.equals('/foo/bar', response.request.path); 41 | })['catch'](fail); 42 | }, 43 | 'should prepend prefix before path, not adding extra slash between path segments': function () { 44 | var client = pathPrefix( 45 | function (request) { return { request: request }; }, 46 | { prefix: '/foo/' } 47 | ); 48 | return client({ path: 'bar' }).then(function (response) { 49 | assert.equals('/foo/bar', response.request.path); 50 | })['catch'](fail); 51 | }, 52 | 'should not prepend prefix before a fully qualified path': function () { 53 | var client = pathPrefix( 54 | function (request) { return { request: request }; }, 55 | { prefix: '/foo' } 56 | ); 57 | return client({ path: 'http://www.example.com/' }).then(function (response) { 58 | assert.equals('http://www.example.com/', response.request.path); 59 | })['catch'](fail); 60 | }, 61 | 'should have the default client as the parent by default': function () { 62 | assert.same(rest, pathPrefix().skip()); 63 | }, 64 | 'should support interceptor wrapping': function () { 65 | assert(typeof pathPrefix().wrap === 'function'); 66 | } 67 | }); 68 | 69 | }); 70 | 71 | }( 72 | this.buster || require('buster'), 73 | typeof define === 'function' && define.amd ? define : function (id, factory) { 74 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 75 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 76 | factory(function (moduleId) { 77 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 78 | }); 79 | } 80 | // Boilerplate for AMD and Node 81 | )); 82 | -------------------------------------------------------------------------------- /test/interceptor/retry-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Jeremy Grelle 6 | * @author Scott Andrews 7 | */ 8 | 9 | (function (buster, define) { 10 | 'use strict'; 11 | 12 | var assert, fail, failOnThrow; 13 | 14 | assert = buster.assertions.assert; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/interceptor/retry-test', function (require) { 19 | 20 | var interceptor, retry, rest, when; 21 | 22 | interceptor = require('rest/interceptor'); 23 | retry = require('rest/interceptor/retry'); 24 | rest = require('rest'); 25 | when = require('when'); 26 | 27 | buster.testCase('rest/interceptor/retry', { 28 | 'should retry until successful': function () { 29 | var count = 0, client = retry( 30 | function (request) { 31 | count += 1; 32 | if (count === 2) { 33 | return { request: request, status: { code: 200 } }; 34 | } else { 35 | return when.reject({ request: request, error: 'Thrown by fake client' }); 36 | } 37 | } 38 | ); 39 | return client({}).then(function (response) { 40 | assert.equals(200, response.status.code); 41 | })['catch'](fail); 42 | }, 43 | 'should accept custom config': function () { 44 | var count = 0, client, start, config; 45 | 46 | start = new Date().getTime(); 47 | config = { initial: 10, multiplier: 3, max: 20 }; 48 | client = retry( 49 | function (request) { 50 | count += 1; 51 | if (count === 4) { 52 | return { request: request, status: { code: 200 } }; 53 | } else { 54 | return when.reject({ request: request, error: 'Thrown by fake client' }); 55 | } 56 | }, 57 | config 58 | ); 59 | 60 | return client({}).then(function (response) { 61 | var durration = Date.now() - start; 62 | assert.equals(200, response.status.code); 63 | assert.equals(count, 4); 64 | assert(40 <= durration); 65 | })['catch'](fail); 66 | }, 67 | 'should not make propagate request if marked as canceled': function () { 68 | var parent, client, request, response; 69 | 70 | parent = this.spy(function (request) { 71 | return when.reject({ request: request }); 72 | }); 73 | client = retry(parent, { initial: 10 }); 74 | 75 | request = {}; 76 | response = client(request).then( 77 | fail, 78 | failOnThrow(function (response) { 79 | assert(request.canceled); 80 | assert.equals('precanceled', response.error); 81 | assert.same(1, parent.callCount); 82 | }) 83 | ); 84 | request.canceled = true; 85 | 86 | return response; 87 | }, 88 | 'should have the default client as the parent by default': function () { 89 | assert.same(rest, retry().skip()); 90 | }, 91 | 'should support interceptor wrapping': function () { 92 | assert(typeof retry().wrap === 'function'); 93 | } 94 | }); 95 | 96 | }); 97 | 98 | }( 99 | this.buster || require('buster'), 100 | typeof define === 'function' && define.amd ? define : function (id, factory) { 101 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 102 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 103 | factory(function (moduleId) { 104 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 105 | }); 106 | } 107 | // Boilerplate for AMD and Node 108 | )); 109 | -------------------------------------------------------------------------------- /test/interceptor/template-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/interceptor/template-test', function (require) { 18 | 19 | var template, rest; 20 | 21 | template = require('rest/interceptor/template'); 22 | rest = require('rest'); 23 | 24 | function parent(request) { 25 | return { request: request }; 26 | } 27 | 28 | buster.testCase('rest/interceptor/template', { 29 | 'should apply the params to the path template': function () { 30 | var client, config; 31 | 32 | config = {}; 33 | client = template(parent, config); 34 | 35 | return client({ path: 'http://example.com/dictionary{/term:1,term}{?lang}', params: { term: 'hypermedia' } }).then(function (response) { 36 | assert.same('http://example.com/dictionary/h/hypermedia', response.request.path); 37 | refute('params' in response.request); 38 | })['catch'](fail); 39 | }, 40 | 'should apply the template and params from the config if not defined on the request': function () { 41 | var client, config; 42 | 43 | config = { template: 'http://example.com/dictionary{/term:1,term}{?lang}', params: { term: 'hypermedia' } }; 44 | client = template(parent, config); 45 | 46 | return client().then(function (response) { 47 | assert.same('http://example.com/dictionary/h/hypermedia', response.request.path); 48 | refute('params' in response.request); 49 | })['catch'](fail); 50 | }, 51 | 'should individually mix config params into the request': function () { 52 | var client, config; 53 | 54 | config = { params: { lang: 'en-us' } }; 55 | client = template(parent, config); 56 | 57 | return client({ path: 'http://example.com/dictionary{/term:1,term}{?lang}', params: { term: 'hypermedia' } }).then(function (response) { 58 | assert.same('http://example.com/dictionary/h/hypermedia?lang=en-us', response.request.path); 59 | refute('params' in response.request); 60 | })['catch'](fail); 61 | }, 62 | 'should ignore missing and overdefined params': function () { 63 | var client, config; 64 | 65 | config = {}; 66 | client = template(parent, config); 67 | 68 | return client({ path: 'http://example.com/dictionary{/term:1,term}{?lang}', params: { q: 'hypermedia' } }).then(function (response) { 69 | assert.same('http://example.com/dictionary', response.request.path); 70 | refute('params' in response.request); 71 | })['catch'](fail); 72 | }, 73 | 'should have the default client as the parent by default': function () { 74 | assert.same(rest, template().skip()); 75 | }, 76 | 'should support interceptor wrapping': function () { 77 | assert(typeof template().wrap === 'function'); 78 | } 79 | }); 80 | 81 | }); 82 | 83 | }( 84 | this.buster || require('buster'), 85 | typeof define === 'function' && define.amd ? define : function (id, factory) { 86 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 87 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 88 | factory(function (moduleId) { 89 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 90 | }); 91 | } 92 | // Boilerplate for AMD and Node 93 | )); 94 | -------------------------------------------------------------------------------- /test/mime-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/mime-test', function (require) { 18 | 19 | var mime; 20 | 21 | mime = require('rest/mime'); 22 | 23 | buster.testCase('rest/mime', { 24 | 'should parse plain mime types': function () { 25 | var parsed = mime.parse('text/plain'); 26 | assert.equals(parsed.raw, 'text/plain'); 27 | assert.equals(parsed.type, 'text/plain'); 28 | assert.equals(parsed.suffix, ''); 29 | assert.equals(parsed.params, {}); 30 | }, 31 | 'should parse suffixed mime types': function () { 32 | var parsed = mime.parse('application/hal+json'); 33 | assert.equals(parsed.raw, 'application/hal+json'); 34 | assert.equals(parsed.type, 'application/hal'); 35 | assert.equals(parsed.suffix, '+json'); 36 | assert.equals(parsed.params, {}); 37 | }, 38 | 'should parse paramerters from mime types': function () { 39 | var parsed = mime.parse('text/plain; charset=ascii; foo=bar'); 40 | assert.equals(parsed.raw, 'text/plain; charset=ascii; foo=bar'); 41 | assert.equals(parsed.type, 'text/plain'); 42 | assert.equals(parsed.suffix, ''); 43 | assert.equals(parsed.params, { charset: 'ascii', foo: 'bar' }); 44 | }, 45 | 'should parse a naked mime suffix': function () { 46 | var parsed = mime.parse('+json'); 47 | assert.equals(parsed.raw, '+json'); 48 | assert.equals(parsed.type, ''); 49 | assert.equals(parsed.suffix, '+json'); 50 | assert.equals(parsed.params, {}); 51 | } 52 | }); 53 | 54 | }); 55 | 56 | }( 57 | this.buster || require('buster'), 58 | typeof define === 'function' && define.amd ? define : function (id, factory) { 59 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 60 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 61 | factory(function (moduleId) { 62 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 63 | }); 64 | } 65 | // Boilerplate for AMD and Node 66 | )); 67 | -------------------------------------------------------------------------------- /test/mime/registry-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/mime/registry-test', function (require) { 18 | 19 | var mimeRegistry, when, registry; 20 | 21 | mimeRegistry = require('rest/mime/registry'); 22 | when = require('when'); 23 | 24 | buster.testCase('rest/mime/registry', { 25 | setUp: function () { 26 | registry = mimeRegistry.child(); 27 | }, 28 | 'should discover unregisted converter': function () { 29 | return registry.lookup('text/plain').then(function (converter) { 30 | assert.isFunction(converter.read); 31 | assert.isFunction(converter.write); 32 | })['catch'](fail); 33 | }, 34 | 'should return registed converter': function () { 35 | var converter = {}; 36 | registry.register('application/vnd.com.example', converter); 37 | return registry.lookup('application/vnd.com.example').then(function (c) { 38 | assert.same(converter, c); 39 | })['catch'](fail); 40 | }, 41 | 'should reject for non-existant converter': function () { 42 | return registry.lookup('application/bogus').then( 43 | fail, 44 | function () { 45 | assert(true); 46 | } 47 | ); 48 | }, 49 | 'should resolve converters from parent registries': function () { 50 | var child, converter; 51 | child = registry.child(); 52 | converter = {}; 53 | registry.register('application/vnd.com.example', converter); 54 | return child.lookup('application/vnd.com.example').then(function (c) { 55 | assert.same(converter, c); 56 | })['catch'](fail); 57 | }, 58 | 'should override parent registries when registering in a child': function () { 59 | var child, converterParent, converterChild; 60 | child = registry.child(); 61 | converterParent = {}; 62 | converterChild = {}; 63 | registry.register('application/vnd.com.example', converterParent); 64 | child.register('application/vnd.com.example', converterChild); 65 | return child.lookup('application/vnd.com.example').then(function (c) { 66 | assert.same(converterChild, c); 67 | })['catch'](fail); 68 | }, 69 | 'should not have any side effects in a parent registry from a child': function () { 70 | var child, converterParent, converterChild; 71 | child = registry.child(); 72 | converterParent = {}; 73 | converterChild = {}; 74 | registry.register('application/vnd.com.example', converterParent); 75 | child.register('application/vnd.com.example', converterChild); 76 | return registry.lookup('application/vnd.com.example').then(function (c) { 77 | assert.same(converterParent, c); 78 | })['catch'](fail); 79 | }, 80 | 'should ignore charset in mime resolution': function () { 81 | var converter = {}; 82 | registry.register('application/vnd.com.example', converter); 83 | return registry.lookup('application/vnd.com.example;charset=utf-8').then(function (c) { 84 | assert.same(converter, c); 85 | })['catch'](fail); 86 | }, 87 | 'should ignore suffix in mime resolution': function () { 88 | var converter = {}; 89 | registry.register('application/vnd.com.example', converter); 90 | return registry.lookup('application/vnd.com.example+foo').then(function (c) { 91 | assert.same(converter, c); 92 | })['catch'](fail); 93 | }, 94 | 'should fallback to suffix if mime type is not resolved': function () { 95 | var converter = {}; 96 | registry.register('+foo', converter); 97 | return registry.lookup('application/vnd.com.example+foo').then(function (c) { 98 | assert.same(converter, c); 99 | })['catch'](fail); 100 | }, 101 | 'should invoke the delegate mime converter': function () { 102 | var converter = { 103 | read: function (obj) { 104 | return 'read ' + obj; 105 | }, 106 | write: function (obj) { 107 | return 'write ' + obj; 108 | } 109 | }; 110 | registry.register('+bar', registry.delegate('+foo')); 111 | registry.register('+foo', converter); 112 | return registry.lookup('application/vnd.com.example+foo').then(function (converter) { 113 | assert.same('read hello', converter.read('hello')); 114 | assert.same('write world', converter.write('world')); 115 | }); 116 | } 117 | }); 118 | 119 | }); 120 | 121 | }( 122 | this.buster || require('buster'), 123 | typeof define === 'function' && define.amd ? define : function (id, factory) { 124 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 125 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 126 | factory(function (moduleId) { 127 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 128 | }); 129 | } 130 | // Boilerplate for AMD and Node 131 | )); 132 | -------------------------------------------------------------------------------- /test/mime/type/application/json-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, undef; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/mime/type/application/json-test', function (require) { 17 | 18 | var json = require('rest/mime/type/application/json'); 19 | 20 | buster.testCase('rest/mime/type/application/json', { 21 | 'should read json': function () { 22 | assert.equals({ foo: 'bar' }, json.read('{"foo":"bar"}')); 23 | }, 24 | 'should stringify json': function () { 25 | assert.equals('{"foo":"bar"}', json.write({ foo: 'bar' })); 26 | }, 27 | 'should use provided reviver and replacer': function () { 28 | var reviver, replacer, customJson; 29 | 30 | reviver = function reviver() {}; 31 | replacer = []; 32 | customJson = json.extend(reviver, replacer); 33 | 34 | assert.equals(undef, customJson.read('{"foo":"bar"}')); 35 | assert.equals('{}', customJson.write({ foo: 'bar' })); 36 | 37 | // old json convert is unmodified 38 | assert.equals({ foo: 'bar' }, json.read('{"foo":"bar"}')); 39 | assert.equals('{"foo":"bar"}', json.write({ foo: 'bar' })); 40 | } 41 | }); 42 | 43 | }); 44 | 45 | }( 46 | this.buster || require('buster'), 47 | typeof define === 'function' && define.amd ? define : function (id, factory) { 48 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 49 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 50 | factory(function (moduleId) { 51 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 52 | }); 53 | } 54 | // Boilerplate for AMD and Node 55 | )); 56 | -------------------------------------------------------------------------------- /test/mime/type/application/x-www-form-urlencoded-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/mime/type/application/x-www-form-urlencoded-test', function (require) { 17 | 18 | var encodeder = require('rest/mime/type/application/x-www-form-urlencoded'); 19 | 20 | buster.testCase('rest/mime/type/application/x-www-form-urlencoded', { 21 | 'should place an eqauls sign between value pairs': function () { 22 | assert.equals('foo=bar&bleep=bloop', encodeder.write({ foo: 'bar', bleep: 'bloop' })); 23 | }, 24 | 'should treat array as multiple values with the same name': function () { 25 | assert.equals('foo=bar&foo=bloop', encodeder.write({ foo: [ 'bar', 'bloop'] })); 26 | }, 27 | 'should url encode names and values': function () { 28 | assert.equals('fo%3Do=b%26ar', encodeder.write({ 'fo=o': 'b&ar' })); 29 | }, 30 | 'should encode spaces as plus': function () { 31 | assert.equals('fo+o=b+ar', encodeder.write({ 'fo o': 'b ar' })); 32 | }, 33 | 'should not include an equals if their is no value': function () { 34 | assert.equals('foo', encodeder.write({ 'foo': undefined })); 35 | assert.equals('foo', encodeder.write({ 'foo': null })); 36 | assert.equals('foo=', encodeder.write({ 'foo': '' })); 37 | }, 38 | 'should parse an eqauls sign between value pairs': function () { 39 | var obj = encodeder.read('foo=bar&bleep=bloop'); 40 | assert.equals('bar', obj.foo); 41 | assert.equals('bloop', obj.bleep); 42 | }, 43 | 'should parse multiple values with the same name as an array': function () { 44 | var obj = encodeder.read('foo=bar&foo=bloop'); 45 | assert.equals('bar', obj.foo[0]); 46 | assert.equals('bloop', obj.foo[1]); 47 | }, 48 | 'should url decode names and values': function () { 49 | var obj = encodeder.read('fo%3Do=b%26ar'); 50 | assert.equals('b&ar', obj['fo=o']); 51 | }, 52 | 'should decode a plus as a space': function () { 53 | var obj = encodeder.read('fo+o=b+ar'); 54 | assert.equals('b ar', obj['fo o']); 55 | }, 56 | 'should parse missing value as null': function () { 57 | var obj = encodeder.read('foo'); 58 | assert.same(null, obj.foo); 59 | } 60 | }); 61 | 62 | }); 63 | 64 | }( 65 | this.buster || require('buster'), 66 | typeof define === 'function' && define.amd ? define : function (id, factory) { 67 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 68 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 69 | factory(function (moduleId) { 70 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 71 | }); 72 | } 73 | // Boilerplate for AMD and Node 74 | )); 75 | -------------------------------------------------------------------------------- /test/mime/type/multipart/form-data-test-browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Michael Jackson 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/mime/type/multipart/form-data-test', function (require) { 17 | 18 | var encoder = require('rest/mime/type/multipart/form-data'); 19 | 20 | buster.testCase('rest/mime/type/multipart/form-data', { 21 | requiresSupportFor: { FormData: typeof FormData !== 'undefined' }, 22 | 'should pass a FormData object through unmodified': function () { 23 | var data = new FormData(); 24 | assert.same(encoder.write(data), data); 25 | }, 26 | 'should encode a form element as FormData': function () { 27 | var form = document.createElement('form'); 28 | assert.hasPrototype(encoder.write(form), FormData.prototype); 29 | }, 30 | 'should encode a plain object as FormData': function () { 31 | assert.hasPrototype(encoder.write({ a: 'string', b: 5 }), FormData.prototype); 32 | }, 33 | 'should throw when given a non-object': function () { 34 | assert.exception(function () { 35 | encoder.write('hello world'); 36 | }, 'Error'); 37 | } 38 | }); 39 | 40 | }); 41 | 42 | }( 43 | this.buster || require('buster'), 44 | typeof define === 'function' && define.amd ? define : function (id, factory) { 45 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 46 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 47 | factory(function (moduleId) { 48 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 49 | }); 50 | } 51 | // Boilerplate for AMD and Node 52 | )); 53 | -------------------------------------------------------------------------------- /test/mime/type/text/plain-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/mime/type/text/plain-test', function (require) { 17 | 18 | var plain = require('rest/mime/type/text/plain'); 19 | 20 | buster.testCase('rest/mime/type/text/plain', { 21 | 'should not change when writing string values': function () { 22 | assert.equals('7', plain.write('7')); 23 | }, 24 | 'should use the string representation for reading non-string values': function () { 25 | assert.equals('7', plain.write(7)); 26 | }, 27 | 'should not change when reading string values': function () { 28 | assert.equals('7', plain.read('7')); 29 | } 30 | }); 31 | 32 | }); 33 | 34 | }( 35 | this.buster || require('buster'), 36 | typeof define === 'function' && define.amd ? define : function (id, factory) { 37 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 38 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 39 | factory(function (moduleId) { 40 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 41 | }); 42 | } 43 | // Boilerplate for AMD and Node 44 | )); 45 | -------------------------------------------------------------------------------- /test/node-test-node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/node-test', function (require) { 19 | 20 | var rest, defaultClient, node; 21 | 22 | rest = require('rest/node'); 23 | defaultClient = require('rest/client/default'); 24 | node = require('rest/client/node'); 25 | 26 | buster.testCase('rest/node', { 27 | 'should be the default client': function () { 28 | assert.same(defaultClient, rest); 29 | }, 30 | 'should wrap the node client': function () { 31 | assert.same(node, rest.getDefaultClient()); 32 | } 33 | }); 34 | 35 | }); 36 | 37 | }( 38 | this.buster || require('buster'), 39 | typeof define === 'function' && define.amd ? define : function (id, factory) { 40 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 41 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 42 | factory(function (moduleId) { 43 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 44 | }); 45 | } 46 | // Boilerplate for AMD and Node 47 | )); 48 | -------------------------------------------------------------------------------- /test/rest-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test', function (require) { 17 | 18 | var rest = require('rest'), 19 | interceptor = require('rest/interceptor'); 20 | 21 | function stubClient(request) { 22 | return { request: request }; 23 | } 24 | 25 | var stubInterceptor = interceptor(); 26 | 27 | buster.testCase('rest', { 28 | setUp: function () { 29 | rest.resetDefaultClient(); 30 | }, 31 | tearDown: function () { 32 | rest.resetDefaultClient(); 33 | }, 34 | 'should return a client by default': function () { 35 | assert.equals('function', typeof rest.getDefaultClient()); 36 | }, 37 | 'should use the provided client as a default': function () { 38 | rest.setDefaultClient(stubClient); 39 | assert.same(stubClient, rest.getDefaultClient()); 40 | assert.equals('request', rest('request').request); 41 | }, 42 | 'should restore the platform default client': function () { 43 | var client = rest.getDefaultClient(); 44 | rest.setDefaultClient(stubClient); 45 | refute.same(client, rest.getDefaultClient()); 46 | rest.resetDefaultClient(); 47 | assert.same(client, rest.getDefaultClient()); 48 | }, 49 | 'should wrap off the default client, using the lastest default client': function () { 50 | var client = rest.wrap(stubInterceptor); 51 | rest.setDefaultClient(stubClient); 52 | refute.same(client, stubClient); 53 | assert.equals('request', rest('request').request); 54 | } 55 | }); 56 | 57 | }); 58 | 59 | }( 60 | this.buster || require('buster'), 61 | typeof define === 'function' && define.amd ? define : function (id, factory) { 62 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 63 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 64 | factory(function (moduleId) { 65 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 66 | }); 67 | } 68 | // Boilerplate for AMD and Node 69 | )); 70 | -------------------------------------------------------------------------------- /test/run.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | define('rest-test/test/run', ['curl/_privileged', 'domReady!'], function (curl) { 12 | 13 | var modules = Object.keys(curl.cache).filter(function (moduleId) { 14 | return moduleId.match(/-test(-browser)?$/); 15 | }); 16 | 17 | buster.testRunner.timeout = 5000; 18 | define('rest-test/test/run-faux', modules, function () { 19 | buster.run(); 20 | }); 21 | 22 | }); 23 | 24 | }( 25 | this.buster || require('buster'), 26 | typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } 27 | // Boilerplate for AMD and Node 28 | )); 29 | -------------------------------------------------------------------------------- /test/util/attempt-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/util/attempt-test', function (require) { 19 | 20 | var attempt = require('rest/util/attempt'); 21 | 22 | buster.testCase('rest/util/attempt', { 23 | 'resolves with returned values': function () { 24 | function work() { 25 | return 'hello'; 26 | } 27 | 28 | return attempt(work).then( 29 | function (value) { 30 | assert.equals('hello', value); 31 | }, 32 | fail 33 | ); 34 | }, 35 | 'rejects with thrown values': function () { 36 | function work() { 37 | throw 'world'; 38 | } 39 | 40 | return attempt(work).then( 41 | fail, 42 | failOnThrow(function (value) { 43 | assert.equals('world', value); 44 | }) 45 | ); 46 | } 47 | }); 48 | }); 49 | 50 | }( 51 | this.buster || require('buster'), 52 | typeof define === 'function' && define.amd ? define : function (id, factory) { 53 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 54 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 55 | factory(function (moduleId) { 56 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 57 | }); 58 | } 59 | // Boilerplate for AMD and Node 60 | )); 61 | -------------------------------------------------------------------------------- /test/util/base64-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/base64-test', function (require) { 17 | 18 | var base64 = require('rest/util/base64'); 19 | 20 | buster.testCase('rest/util/base64', { 21 | 'should base64 encode strings': function () { 22 | assert.equals('Zm9v', base64.encode('foo')); 23 | }, 24 | 'should base64 decode strings': function () { 25 | assert.equals('foo', base64.decode('Zm9v')); 26 | } 27 | }); 28 | 29 | }); 30 | 31 | }( 32 | this.buster || require('buster'), 33 | typeof define === 'function' && define.amd ? define : function (id, factory) { 34 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 35 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 36 | factory(function (moduleId) { 37 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 38 | }); 39 | } 40 | // Boilerplate for AMD and Node 41 | )); 42 | -------------------------------------------------------------------------------- /test/util/delay-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/util/delay-test', function (require) { 19 | 20 | var delay = require('rest/util/delay'); 21 | 22 | buster.testCase('rest/util/delay', { 23 | 'delays promise resolution': function () { 24 | var start = Date.now(); 25 | return delay(20, 'hello').then( 26 | function (value) { 27 | assert(Date.now() - start >= 10); 28 | assert.equals('hello', value); 29 | }, 30 | fail 31 | ); 32 | }, 33 | 'delayed from provided promise resolution': function () { 34 | var start, trigger; 35 | start = Date.now(); 36 | trigger = new Promise(function (resolve) { 37 | setTimeout(function () { 38 | resolve('world'); 39 | }, 20); 40 | }); 41 | return delay(20, trigger).then( 42 | function (value) { 43 | assert(Date.now() - start >= 20); 44 | assert.equals('world', value); 45 | }, 46 | fail 47 | ); 48 | }, 49 | 'rejections are not delayed': function () { 50 | var start = Date.now(); 51 | return delay(1000, Promise.reject('hello')).then( 52 | fail, 53 | failOnThrow(function (value) { 54 | assert(Date.now() - start < 100); 55 | assert.equals('hello', value); 56 | }) 57 | ); 58 | } 59 | }); 60 | }); 61 | 62 | }( 63 | this.buster || require('buster'), 64 | typeof define === 'function' && define.amd ? define : function (id, factory) { 65 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 66 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 67 | factory(function (moduleId) { 68 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 69 | }); 70 | } 71 | // Boilerplate for AMD and Node 72 | )); 73 | -------------------------------------------------------------------------------- /test/util/find-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/find-test', function (require) { 17 | 18 | var find = require('rest/util/find'); 19 | 20 | buster.testCase('rest/util/find', { 21 | 'should find objects that contain a given property name': function () { 22 | var graph, spy; 23 | 24 | graph = { foo: { foo: {}, bar: { bar: { foo: {} } } } }; 25 | spy = this.spy(); 26 | 27 | find.findProperties(graph, 'foo', spy); 28 | 29 | assert.same(3, spy.callCount); 30 | assert.calledWith(spy, graph.foo, graph, 'foo'); 31 | assert.calledWith(spy, graph.foo.foo, graph.foo, 'foo'); 32 | assert.calledWith(spy, graph.foo.bar.bar.foo, graph.foo.bar.bar, 'foo'); 33 | } 34 | }); 35 | }); 36 | 37 | }( 38 | this.buster || require('buster'), 39 | typeof define === 'function' && define.amd ? define : function (id, factory) { 40 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 41 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 42 | factory(function (moduleId) { 43 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 44 | }); 45 | } 46 | // Boilerplate for AMD and Node 47 | )); 48 | -------------------------------------------------------------------------------- /test/util/lazyPromise-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | 17 | define('rest-test/util/lazyPromise-test', function (require) { 18 | 19 | var lazyPromise = require('rest/util/lazyPromise'); 20 | 21 | buster.testCase('rest/util/lazyPromise', { 22 | 'should not start work until a handler is attached': function () { 23 | var promise, spy; 24 | 25 | spy = this.spy(function () { return 'lazy'; }); 26 | promise = lazyPromise(spy); 27 | 28 | refute.called(spy); 29 | 30 | return promise.then( 31 | function (value) { 32 | assert.called(spy); 33 | assert.equals('lazy', value); 34 | }, 35 | fail 36 | ); 37 | }, 38 | 'should reject if the work function throws': function () { 39 | var promise, spy; 40 | 41 | spy = this.spy(function () { throw 'lazy'; }); 42 | promise = lazyPromise(spy); 43 | 44 | refute.called(spy); 45 | 46 | return promise.then( 47 | fail, 48 | function (value) { 49 | assert.called(spy); 50 | assert.equals('lazy', value); 51 | } 52 | ); 53 | } 54 | }); 55 | }); 56 | 57 | }( 58 | this.buster || require('buster'), 59 | typeof define === 'function' && define.amd ? define : function (id, factory) { 60 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 61 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 62 | factory(function (moduleId) { 63 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 64 | }); 65 | } 66 | // Boilerplate for AMD and Node 67 | )); 68 | -------------------------------------------------------------------------------- /test/util/mixin-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/mixin-test', function (require) { 17 | 18 | var mixin = require('rest/util/mixin'); 19 | 20 | buster.testCase('rest/util/mixin', { 21 | 'should return an emtpy object for no args': function () { 22 | var mixed, prop; 23 | mixed = mixin(); 24 | assert(mixed); 25 | for (prop in mixed) { 26 | /*jshint forin:false */ 27 | refute(mixed.hasOwnProperty(prop)); 28 | } 29 | }, 30 | 'should return original object': function () { 31 | var orig, mixed; 32 | orig = { foo: 'bar' }; 33 | mixed = mixin(orig); 34 | assert.same(orig, mixed); 35 | }, 36 | 'should return original object, supplemented': function () { 37 | var orig, supplemented, mixed; 38 | orig = { foo: 'bar' }; 39 | supplemented = { foo: 'foo' }; 40 | mixed = mixin(orig, supplemented); 41 | assert.same(orig, mixed); 42 | assert.equals('foo', mixed.foo); 43 | } 44 | }); 45 | 46 | }); 47 | 48 | }( 49 | this.buster || require('buster'), 50 | typeof define === 'function' && define.amd ? define : function (id, factory) { 51 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 52 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 53 | factory(function (moduleId) { 54 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 55 | }); 56 | } 57 | // Boilerplate for AMD and Node 58 | )); 59 | -------------------------------------------------------------------------------- /test/util/normalizeHeaderName-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/normalizeHeaderName-test', function (require) { 17 | 18 | var normalizeHeaderName = require('rest/util/normalizeHeaderName'); 19 | 20 | buster.testCase('rest/util/normalizeHeaderName', { 21 | 'should normalize header names': function () { 22 | assert.equals('Accept', normalizeHeaderName('accept')); 23 | assert.equals('Accept', normalizeHeaderName('ACCEPT')); 24 | assert.equals('Content-Length', normalizeHeaderName('content-length')); 25 | assert.equals('X-Some-Custom-Header', normalizeHeaderName('x-some-custom-header')); 26 | } 27 | }); 28 | 29 | }); 30 | 31 | }( 32 | this.buster || require('buster'), 33 | typeof define === 'function' && define.amd ? define : function (id, factory) { 34 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 35 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 36 | factory(function (moduleId) { 37 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 38 | }); 39 | } 40 | // Boilerplate for AMD and Node 41 | )); 42 | -------------------------------------------------------------------------------- /test/util/pubsub-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/pubsub-test', function (require) { 17 | 18 | var pubsub = require('rest/util/pubsub'); 19 | 20 | buster.testCase('rest/util/pubsub', { 21 | 'should pass arguments to subscribed listener': function () { 22 | var callback = this.spy(function (value) { 23 | assert.equals('result', value); 24 | }); 25 | pubsub.subscribe('topic', callback); 26 | pubsub.publish('topic', 'result'); 27 | assert.called(callback); 28 | }, 29 | 'should ignore publish with no listeners': function () { 30 | pubsub.publish('topic', 'result'); 31 | assert(true); 32 | }, 33 | 'should unsubscribe listener after publish': function () { 34 | var callback = this.spy(function (value) { 35 | assert.equals('result', value); 36 | }); 37 | pubsub.subscribe('topic', callback); 38 | pubsub.publish('topic', 'result'); 39 | pubsub.publish('topic', 'result2'); 40 | assert.calledOnce(callback); 41 | }, 42 | 'should only call most recent listener': function () { 43 | var callback1, callback2; 44 | callback1 = this.spy(); 45 | callback2 = this.spy(function (value) { 46 | assert.equals('result', value); 47 | }); 48 | pubsub.subscribe('topic', callback1); 49 | pubsub.subscribe('topic', callback2); 50 | pubsub.publish('topic', 'result'); 51 | assert.calledOnce(callback2); 52 | refute.called(callback1); 53 | } 54 | }); 55 | 56 | }); 57 | 58 | }( 59 | this.buster || require('buster'), 60 | typeof define === 'function' && define.amd ? define : function (id, factory) { 61 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 62 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 63 | factory(function (moduleId) { 64 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 65 | }); 66 | } 67 | // Boilerplate for AMD and Node 68 | )); 69 | -------------------------------------------------------------------------------- /test/version-test-node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/version-test', function (require) { 17 | 18 | var bowerJson, packageJson; 19 | 20 | bowerJson = require('rest/bower.json'); 21 | packageJson = require('rest/package.json'); 22 | 23 | buster.testCase('rest/version', { 24 | 'should have the same name for package.json and bower.json': function () { 25 | assert.same(bowerJson.name, packageJson.name); 26 | }, 27 | 'should have the same version for package.json and bower.json': function () { 28 | assert.same(bowerJson.version, packageJson.version); 29 | }, 30 | 'should have the same depenencies for package.json and bpwer.json': function () { 31 | // this may not always hold true, but it currently does 32 | assert.equals(bowerJson.dependencies, packageJson.dependencies); 33 | } 34 | }); 35 | 36 | }); 37 | 38 | }( 39 | this.buster || require('buster'), 40 | typeof define === 'function' && define.amd ? define : function (id, factory) { 41 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 42 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 43 | factory(function (moduleId) { 44 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 45 | }); 46 | } 47 | // Boilerplate for AMD and Node 48 | )); 49 | -------------------------------------------------------------------------------- /util/Promise.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /*global Promise */ 11 | 12 | 13 | module.exports = Promise; 14 | -------------------------------------------------------------------------------- /util/attempt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Attempt to invoke a function capturing the resulting value as a Promise 12 | * 13 | * If the method throws, the caught value used to reject the Promise. 14 | * 15 | * @param {function} work function to invoke 16 | * @returns {Promise} Promise for the output of the work function 17 | */ 18 | function attempt(work) { 19 | try { 20 | return Promise.resolve(work()); 21 | } 22 | catch (e) { 23 | return Promise.reject(e); 24 | } 25 | } 26 | 27 | module.exports = attempt; 28 | -------------------------------------------------------------------------------- /util/base64.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Nicholas C. Zakas. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | /* 24 | * Base 64 implementation in JavaScript 25 | * Original source available at https://raw.github.com/nzakas/computer-science-in-javascript/02a2745b4aa8214f2cae1bf0b15b447ca1a91b23/encodings/base64/base64.js 26 | * 27 | * Linter refinement by Scott Andrews 28 | */ 29 | 30 | 'use strict'; 31 | 32 | /*jshint bitwise: false */ 33 | 34 | var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 35 | 36 | /** 37 | * Base64-encodes a string of text. 38 | * 39 | * @param {string} text The text to encode. 40 | * @return {string} The base64-encoded string. 41 | */ 42 | function base64Encode(text) { 43 | 44 | if (/([^\u0000-\u00ff])/.test(text)) { 45 | throw new Error('Can\'t base64 encode non-ASCII characters.'); 46 | } 47 | 48 | var i = 0, 49 | cur, prev, byteNum, 50 | result = []; 51 | 52 | while (i < text.length) { 53 | 54 | cur = text.charCodeAt(i); 55 | byteNum = i % 3; 56 | 57 | switch (byteNum) { 58 | case 0: //first byte 59 | result.push(digits.charAt(cur >> 2)); 60 | break; 61 | 62 | case 1: //second byte 63 | result.push(digits.charAt((prev & 3) << 4 | (cur >> 4))); 64 | break; 65 | 66 | case 2: //third byte 67 | result.push(digits.charAt((prev & 0x0f) << 2 | (cur >> 6))); 68 | result.push(digits.charAt(cur & 0x3f)); 69 | break; 70 | } 71 | 72 | prev = cur; 73 | i += 1; 74 | } 75 | 76 | if (byteNum === 0) { 77 | result.push(digits.charAt((prev & 3) << 4)); 78 | result.push('=='); 79 | } else if (byteNum === 1) { 80 | result.push(digits.charAt((prev & 0x0f) << 2)); 81 | result.push('='); 82 | } 83 | 84 | return result.join(''); 85 | } 86 | 87 | /** 88 | * Base64-decodes a string of text. 89 | * 90 | * @param {string} text The text to decode. 91 | * @return {string} The base64-decoded string. 92 | */ 93 | function base64Decode(text) { 94 | 95 | //ignore white space 96 | text = text.replace(/\s/g, ''); 97 | 98 | //first check for any unexpected input 99 | if (!(/^[a-z0-9\+\/\s]+\={0,2}$/i.test(text)) || text.length % 4 > 0) { 100 | throw new Error('Not a base64-encoded string.'); 101 | } 102 | 103 | //local variables 104 | var cur, prev, digitNum, 105 | i = 0, 106 | result = []; 107 | 108 | //remove any equals signs 109 | text = text.replace(/\=/g, ''); 110 | 111 | //loop over each character 112 | while (i < text.length) { 113 | 114 | cur = digits.indexOf(text.charAt(i)); 115 | digitNum = i % 4; 116 | 117 | switch (digitNum) { 118 | 119 | //case 0: first digit - do nothing, not enough info to work with 120 | 121 | case 1: //second digit 122 | result.push(String.fromCharCode(prev << 2 | cur >> 4)); 123 | break; 124 | 125 | case 2: //third digit 126 | result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2)); 127 | break; 128 | 129 | case 3: //fourth digit 130 | result.push(String.fromCharCode((prev & 3) << 6 | cur)); 131 | break; 132 | } 133 | 134 | prev = cur; 135 | i += 1; 136 | } 137 | 138 | //return a string 139 | return result.join(''); 140 | 141 | } 142 | 143 | module.exports = { 144 | encode: base64Encode, 145 | decode: base64Decode 146 | }; 147 | -------------------------------------------------------------------------------- /util/delay.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Delay the resolution of a promise 12 | * 13 | * Note: if the value is a promise, the delay starts once the promise 14 | * resolves. 15 | * 16 | * @param {number} wait miliseconds to wait 17 | * @param {Promise|*} [promiseOrValue] value to resolve with 18 | * @returns {Promise} delayed Promise containing the value 19 | */ 20 | function delay(wait, promiseOrValue) { 21 | return Promise.resolve(promiseOrValue).then(function (value) { 22 | return new Promise(function (resolve) { 23 | setTimeout(function () { 24 | resolve(value); 25 | }, wait); 26 | }); 27 | }); 28 | } 29 | 30 | module.exports = delay; 31 | -------------------------------------------------------------------------------- /util/find.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | module.exports = { 11 | 12 | /** 13 | * Find objects within a graph the contain a property of a certain name. 14 | * 15 | * NOTE: this method will not discover object graph cycles. 16 | * 17 | * @param {*} obj object to search on 18 | * @param {string} prop name of the property to search for 19 | * @param {Function} callback function to receive the found properties and their parent 20 | */ 21 | findProperties: function findProperties(obj, prop, callback) { 22 | if (typeof obj !== 'object' || obj === null) { return; } 23 | if (prop in obj) { 24 | callback(obj[prop], obj, prop); 25 | } 26 | Object.keys(obj).forEach(function (key) { 27 | findProperties(obj[key], prop, callback); 28 | }); 29 | } 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /util/lazyPromise.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var attempt = require('./attempt'); 11 | 12 | /** 13 | * Create a promise whose work is started only when a handler is registered. 14 | * 15 | * The work function will be invoked at most once. Thrown values will result 16 | * in promise rejection. 17 | * 18 | * @param {Function} work function whose ouput is used to resolve the 19 | * returned promise. 20 | * @returns {Promise} a lazy promise 21 | */ 22 | function lazyPromise(work) { 23 | var started, resolver, promise, then; 24 | 25 | started = false; 26 | 27 | promise = new Promise(function (resolve, reject) { 28 | resolver = { 29 | resolve: resolve, 30 | reject: reject 31 | }; 32 | }); 33 | then = promise.then; 34 | 35 | promise.then = function () { 36 | if (!started) { 37 | started = true; 38 | attempt(work).then(resolver.resolve, resolver.reject); 39 | } 40 | return then.apply(promise, arguments); 41 | }; 42 | 43 | return promise; 44 | } 45 | 46 | module.exports = lazyPromise; 47 | -------------------------------------------------------------------------------- /util/mixin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var empty = {}; 11 | 12 | /** 13 | * Mix the properties from the source object into the destination object. 14 | * When the same property occurs in more then one object, the right most 15 | * value wins. 16 | * 17 | * @param {Object} dest the object to copy properties to 18 | * @param {Object} sources the objects to copy properties from. May be 1 to N arguments, but not an Array. 19 | * @return {Object} the destination object 20 | */ 21 | function mixin(dest /*, sources... */) { 22 | var i, l, source, name; 23 | 24 | if (!dest) { dest = {}; } 25 | for (i = 1, l = arguments.length; i < l; i += 1) { 26 | source = arguments[i]; 27 | for (name in source) { 28 | if (!(name in dest) || (dest[name] !== source[name] && (!(name in empty) || empty[name] !== source[name]))) { 29 | dest[name] = source[name]; 30 | } 31 | } 32 | } 33 | 34 | return dest; // Object 35 | } 36 | 37 | module.exports = mixin; 38 | -------------------------------------------------------------------------------- /util/normalizeHeaderName.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Normalize HTTP header names using the pseudo camel case. 12 | * 13 | * For example: 14 | * content-type -> Content-Type 15 | * accepts -> Accepts 16 | * x-custom-header-name -> X-Custom-Header-Name 17 | * 18 | * @param {string} name the raw header name 19 | * @return {string} the normalized header name 20 | */ 21 | function normalizeHeaderName(name) { 22 | return name.toLowerCase() 23 | .split('-') 24 | .map(function (chunk) { return chunk.charAt(0).toUpperCase() + chunk.slice(1); }) 25 | .join('-'); 26 | } 27 | 28 | module.exports = normalizeHeaderName; 29 | -------------------------------------------------------------------------------- /util/pubsub.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | // A poor man's pub-sub. A single listener is supported per topic. When 11 | // the topic is published, the listener is unsubscribed. 12 | 13 | var topics = {}; 14 | 15 | /** 16 | * Publishes the message to the topic, invoking the listener. 17 | * 18 | * The listener is unsubscribed from the topic after receiving a message. 19 | * 20 | * @param {string} topic the topic to publish to 21 | * @param {Object} message message to publish 22 | */ 23 | function publish(topic /* , message... */) { 24 | if (!topics[topic]) { return; } 25 | topics[topic].apply({}, Array.prototype.slice.call(arguments, 1)); 26 | // auto cleanup 27 | delete topics[topic]; 28 | } 29 | 30 | /** 31 | * Register a callback function to receive notification of a message published to the topic. 32 | * 33 | * Any existing callback for the topic will be unsubscribed. 34 | * 35 | * @param {string} topic the topic to listen on 36 | * @param {Function} callback the callback to receive the message published to the topic 37 | */ 38 | function subscribe(topic, callback) { 39 | topics[topic] = callback; 40 | } 41 | 42 | module.exports = { 43 | publish: publish, 44 | subscribe: subscribe 45 | }; 46 | -------------------------------------------------------------------------------- /util/responsePromise.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /*jshint latedef: nofunc */ 11 | 12 | var normalizeHeaderName = require('./normalizeHeaderName'); 13 | 14 | function property(promise, name) { 15 | return promise.then( 16 | function (value) { 17 | return value && value[name]; 18 | }, 19 | function (value) { 20 | return Promise.reject(value && value[name]); 21 | } 22 | ); 23 | } 24 | 25 | /** 26 | * Obtain the response entity 27 | * 28 | * @returns {Promise} for the response entity 29 | */ 30 | function entity() { 31 | /*jshint validthis:true */ 32 | return property(this, 'entity'); 33 | } 34 | 35 | /** 36 | * Obtain the response status 37 | * 38 | * @returns {Promise} for the response status 39 | */ 40 | function status() { 41 | /*jshint validthis:true */ 42 | return property(property(this, 'status'), 'code'); 43 | } 44 | 45 | /** 46 | * Obtain the response headers map 47 | * 48 | * @returns {Promise} for the response headers map 49 | */ 50 | function headers() { 51 | /*jshint validthis:true */ 52 | return property(this, 'headers'); 53 | } 54 | 55 | /** 56 | * Obtain a specific response header 57 | * 58 | * @param {String} headerName the header to retrieve 59 | * @returns {Promise} for the response header's value 60 | */ 61 | function header(headerName) { 62 | /*jshint validthis:true */ 63 | headerName = normalizeHeaderName(headerName); 64 | return property(this.headers(), headerName); 65 | } 66 | 67 | /** 68 | * Follow a related resource 69 | * 70 | * The relationship to follow may be define as a plain string, an object 71 | * with the rel and params, or an array containing one or more entries 72 | * with the previous forms. 73 | * 74 | * Examples: 75 | * response.follow('next') 76 | * 77 | * response.follow({ rel: 'next', params: { pageSize: 100 } }) 78 | * 79 | * response.follow([ 80 | * { rel: 'items', params: { projection: 'noImages' } }, 81 | * 'search', 82 | * { rel: 'findByGalleryIsNull', params: { projection: 'noImages' } }, 83 | * 'items' 84 | * ]) 85 | * 86 | * @param {String|Object|Array} rels one, or more, relationships to follow 87 | * @returns ResponsePromise related resource 88 | */ 89 | function follow(rels) { 90 | /*jshint validthis:true */ 91 | rels = [].concat(rels); 92 | 93 | return make(rels.reduce(function (response, rel) { 94 | return response.then(function (response) { 95 | if (typeof rel === 'string') { 96 | rel = { rel: rel }; 97 | } 98 | if (typeof response.entity.clientFor !== 'function') { 99 | throw new Error('Hypermedia response expected'); 100 | } 101 | var client = response.entity.clientFor(rel.rel); 102 | return client({ params: rel.params }); 103 | }); 104 | }, this)); 105 | } 106 | 107 | /** 108 | * Wrap a Promise as an ResponsePromise 109 | * 110 | * @param {Promise} promise the promise for an HTTP Response 111 | * @returns {ResponsePromise} wrapped promise for Response with additional helper methods 112 | */ 113 | function make(promise) { 114 | promise.status = status; 115 | promise.headers = headers; 116 | promise.header = header; 117 | promise.entity = entity; 118 | promise.follow = follow; 119 | return promise; 120 | } 121 | 122 | function responsePromise(obj, callback, errback) { 123 | return make(Promise.resolve(obj).then(callback, errback)); 124 | } 125 | 126 | responsePromise.make = make; 127 | responsePromise.reject = function (val) { 128 | return make(Promise.reject(val)); 129 | }; 130 | responsePromise.promise = function (func) { 131 | return make(new Promise(func)); 132 | }; 133 | 134 | module.exports = responsePromise; 135 | -------------------------------------------------------------------------------- /util/uriEncoder.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var charMap; 11 | 12 | charMap = (function () { 13 | var strings = { 14 | alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 15 | digit: '0123456789' 16 | }; 17 | 18 | strings.genDelims = ':/?#[]@'; 19 | strings.subDelims = '!$&\'()*+,;='; 20 | strings.reserved = strings.genDelims + strings.subDelims; 21 | strings.unreserved = strings.alpha + strings.digit + '-._~'; 22 | strings.url = strings.reserved + strings.unreserved; 23 | strings.scheme = strings.alpha + strings.digit + '+-.'; 24 | strings.userinfo = strings.unreserved + strings.subDelims + ':'; 25 | strings.host = strings.unreserved + strings.subDelims; 26 | strings.port = strings.digit; 27 | strings.pchar = strings.unreserved + strings.subDelims + ':@'; 28 | strings.segment = strings.pchar; 29 | strings.path = strings.segment + '/'; 30 | strings.query = strings.pchar + '/?'; 31 | strings.fragment = strings.pchar + '/?'; 32 | 33 | return Object.keys(strings).reduce(function (charMap, set) { 34 | charMap[set] = strings[set].split('').reduce(function (chars, myChar) { 35 | chars[myChar] = true; 36 | return chars; 37 | }, {}); 38 | return charMap; 39 | }, {}); 40 | }()); 41 | 42 | function encode(str, allowed) { 43 | if (typeof str !== 'string') { 44 | throw new Error('String required for URL encoding'); 45 | } 46 | return str.split('').map(function (myChar) { 47 | if (allowed.hasOwnProperty(myChar)) { 48 | return myChar; 49 | } 50 | var code = myChar.charCodeAt(0); 51 | if (code <= 127) { 52 | var encoded = code.toString(16).toUpperCase(); 53 | return '%' + (encoded.length % 2 === 1 ? '0' : '') + encoded; 54 | } 55 | else { 56 | return encodeURIComponent(myChar).toUpperCase(); 57 | } 58 | }).join(''); 59 | } 60 | 61 | function makeEncoder(allowed) { 62 | allowed = allowed || charMap.unreserved; 63 | return function (str) { 64 | return encode(str, allowed); 65 | }; 66 | } 67 | 68 | function decode(str) { 69 | return decodeURIComponent(str); 70 | } 71 | 72 | module.exports = { 73 | 74 | /* 75 | * Decode URL encoded strings 76 | * 77 | * @param {string} URL encoded string 78 | * @returns {string} URL decoded string 79 | */ 80 | decode: decode, 81 | 82 | /* 83 | * URL encode a string 84 | * 85 | * All but alpha-numerics and a very limited set of punctuation - . _ ~ are 86 | * encoded. 87 | * 88 | * @param {string} string to encode 89 | * @returns {string} URL encoded string 90 | */ 91 | encode: makeEncoder(), 92 | 93 | /* 94 | * URL encode a URL 95 | * 96 | * All character permitted anywhere in a URL are left unencoded even 97 | * if that character is not permitted in that portion of a URL. 98 | * 99 | * Note: This method is typically not what you want. 100 | * 101 | * @param {string} string to encode 102 | * @returns {string} URL encoded string 103 | */ 104 | encodeURL: makeEncoder(charMap.url), 105 | 106 | /* 107 | * URL encode the scheme portion of a URL 108 | * 109 | * @param {string} string to encode 110 | * @returns {string} URL encoded string 111 | */ 112 | encodeScheme: makeEncoder(charMap.scheme), 113 | 114 | /* 115 | * URL encode the user info portion of a URL 116 | * 117 | * @param {string} string to encode 118 | * @returns {string} URL encoded string 119 | */ 120 | encodeUserInfo: makeEncoder(charMap.userinfo), 121 | 122 | /* 123 | * URL encode the host portion of a URL 124 | * 125 | * @param {string} string to encode 126 | * @returns {string} URL encoded string 127 | */ 128 | encodeHost: makeEncoder(charMap.host), 129 | 130 | /* 131 | * URL encode the port portion of a URL 132 | * 133 | * @param {string} string to encode 134 | * @returns {string} URL encoded string 135 | */ 136 | encodePort: makeEncoder(charMap.port), 137 | 138 | /* 139 | * URL encode a path segment portion of a URL 140 | * 141 | * @param {string} string to encode 142 | * @returns {string} URL encoded string 143 | */ 144 | encodePathSegment: makeEncoder(charMap.segment), 145 | 146 | /* 147 | * URL encode the path portion of a URL 148 | * 149 | * @param {string} string to encode 150 | * @returns {string} URL encoded string 151 | */ 152 | encodePath: makeEncoder(charMap.path), 153 | 154 | /* 155 | * URL encode the query portion of a URL 156 | * 157 | * @param {string} string to encode 158 | * @returns {string} URL encoded string 159 | */ 160 | encodeQuery: makeEncoder(charMap.query), 161 | 162 | /* 163 | * URL encode the fragment portion of a URL 164 | * 165 | * @param {string} string to encode 166 | * @returns {string} URL encoded string 167 | */ 168 | encodeFragment: makeEncoder(charMap.fragment) 169 | 170 | }; 171 | -------------------------------------------------------------------------------- /util/uriTemplate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var uriEncoder, operations, prefixRE; 11 | 12 | uriEncoder = require('./uriEncoder'); 13 | 14 | prefixRE = /^([^:]*):([0-9]+)$/; 15 | operations = { 16 | '': { first: '', separator: ',', named: false, empty: '', encoder: uriEncoder.encode }, 17 | '+': { first: '', separator: ',', named: false, empty: '', encoder: uriEncoder.encodeURL }, 18 | '#': { first: '#', separator: ',', named: false, empty: '', encoder: uriEncoder.encodeURL }, 19 | '.': { first: '.', separator: '.', named: false, empty: '', encoder: uriEncoder.encode }, 20 | '/': { first: '/', separator: '/', named: false, empty: '', encoder: uriEncoder.encode }, 21 | ';': { first: ';', separator: ';', named: true, empty: '', encoder: uriEncoder.encode }, 22 | '?': { first: '?', separator: '&', named: true, empty: '=', encoder: uriEncoder.encode }, 23 | '&': { first: '&', separator: '&', named: true, empty: '=', encoder: uriEncoder.encode }, 24 | '=': { reserved: true }, 25 | ',': { reserved: true }, 26 | '!': { reserved: true }, 27 | '@': { reserved: true }, 28 | '|': { reserved: true } 29 | }; 30 | 31 | function apply(operation, expression, params) { 32 | /*jshint maxcomplexity:11 */ 33 | return expression.split(',').reduce(function (result, variable) { 34 | var opts, value; 35 | 36 | opts = {}; 37 | if (variable.slice(-1) === '*') { 38 | variable = variable.slice(0, -1); 39 | opts.explode = true; 40 | } 41 | if (prefixRE.test(variable)) { 42 | var prefix = prefixRE.exec(variable); 43 | variable = prefix[1]; 44 | opts.maxLength = parseInt(prefix[2]); 45 | } 46 | 47 | variable = uriEncoder.decode(variable); 48 | value = params[variable]; 49 | 50 | if (value === void 0 || value === null) { 51 | return result; 52 | } 53 | if (Array.isArray(value)) { 54 | result = value.reduce(function (result, value) { 55 | if (result.length) { 56 | result += opts.explode ? operation.separator : ','; 57 | if (operation.named && opts.explode) { 58 | result += operation.encoder(variable); 59 | result += value.length ? '=' : operation.empty; 60 | } 61 | } 62 | else { 63 | result += operation.first; 64 | if (operation.named) { 65 | result += operation.encoder(variable); 66 | result += value.length ? '=' : operation.empty; 67 | } 68 | } 69 | result += operation.encoder(value); 70 | return result; 71 | }, result); 72 | } 73 | else if (typeof value === 'object') { 74 | result = Object.keys(value).reduce(function (result, name) { 75 | if (result.length) { 76 | result += opts.explode ? operation.separator : ','; 77 | } 78 | else { 79 | result += operation.first; 80 | if (operation.named && !opts.explode) { 81 | result += operation.encoder(variable); 82 | result += value[name].length ? '=' : operation.empty; 83 | } 84 | } 85 | result += operation.encoder(name); 86 | result += opts.explode ? '=' : ','; 87 | result += operation.encoder(value[name]); 88 | return result; 89 | }, result); 90 | } 91 | else { 92 | value = String(value); 93 | if (opts.maxLength) { 94 | value = value.slice(0, opts.maxLength); 95 | } 96 | result += result.length ? operation.separator : operation.first; 97 | if (operation.named) { 98 | result += operation.encoder(variable); 99 | result += value.length ? '=' : operation.empty; 100 | } 101 | result += operation.encoder(value); 102 | } 103 | 104 | return result; 105 | }, ''); 106 | } 107 | 108 | function expandExpression(expression, params) { 109 | var operation; 110 | 111 | operation = operations[expression.slice(0,1)]; 112 | if (operation) { 113 | expression = expression.slice(1); 114 | } 115 | else { 116 | operation = operations['']; 117 | } 118 | 119 | if (operation.reserved) { 120 | throw new Error('Reserved expression operations are not supported'); 121 | } 122 | 123 | return apply(operation, expression, params); 124 | } 125 | 126 | function expandTemplate(template, params) { 127 | var start, end, uri; 128 | 129 | uri = ''; 130 | end = 0; 131 | while (true) { 132 | start = template.indexOf('{', end); 133 | if (start === -1) { 134 | // no more expressions 135 | uri += template.slice(end); 136 | break; 137 | } 138 | uri += template.slice(end, start); 139 | end = template.indexOf('}', start) + 1; 140 | uri += expandExpression(template.slice(start + 1, end - 1), params); 141 | } 142 | 143 | return uri; 144 | } 145 | 146 | module.exports = { 147 | 148 | /** 149 | * Expand a URI Template with parameters to form a URI. 150 | * 151 | * Full implementation (level 4) of rfc6570. 152 | * @see https://tools.ietf.org/html/rfc6570 153 | * 154 | * @param {string} template URI template 155 | * @param {Object} [params] params to apply to the template durring expantion 156 | * @returns {string} expanded URI 157 | */ 158 | expand: expandTemplate 159 | 160 | }; 161 | -------------------------------------------------------------------------------- /wire.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var client, when, pipeline, plugin; 11 | 12 | client = require('./client/default'); 13 | when = require('when'); 14 | pipeline = require('when/pipeline'); 15 | 16 | function normalizeRestFactoryConfig(spec, wire) { 17 | var config = {}; 18 | 19 | config.parent = wire(spec.parent || client); 20 | config.interceptors = when.all((Array.isArray(spec) ? spec : spec.interceptors || []).map(function (interceptorDef) { 21 | var interceptorConfig = interceptorDef.config; 22 | delete interceptorDef.config; 23 | return when.all([ 24 | wire(typeof interceptorDef === 'string' ? { module: interceptorDef } : interceptorDef), 25 | wire(interceptorConfig) 26 | ]).spread(function (interceptor, config) { 27 | return { interceptor: interceptor, config: config }; 28 | }); 29 | })); 30 | 31 | return config; 32 | } 33 | 34 | /** 35 | * Creates a rest client for the "rest" factory. 36 | * @param resolver 37 | * @param spec 38 | * @param wire 39 | */ 40 | function restFactory(resolver, spec, wire) { 41 | var config = normalizeRestFactoryConfig(spec.rest || spec.options, wire); 42 | return config.parent.then(function (parent) { 43 | return config.interceptors.then(function (interceptorDefs) { 44 | pipeline(interceptorDefs.map(function (interceptorDef) { 45 | return function (parent) { 46 | return interceptorDef.interceptor(parent, interceptorDef.config); 47 | }; 48 | }), parent).then(resolver.resolve, resolver.reject); 49 | }); 50 | }); 51 | } 52 | 53 | /** 54 | * The plugin instance. Can be the same for all wiring runs 55 | */ 56 | plugin = { 57 | resolvers: { 58 | client: function () { 59 | throw new Error('rest.js: client! wire reference resolved is deprecated, use \'rest\' facotry instead'); 60 | } 61 | }, 62 | factories: { 63 | rest: restFactory 64 | } 65 | }; 66 | 67 | module.exports = { 68 | wire$plugin: function restPlugin(/* ready, destroyed, options */) { 69 | return plugin; 70 | } 71 | }; 72 | --------------------------------------------------------------------------------