Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1 @@
*.json -diff
./**/*.json -diff
93 changes: 93 additions & 0 deletions lib/autodiscovery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Utility methods used to query a site in order to discover its available
* API endpoints
*
* @module autodiscovery
*/
'use strict';

/*jshint -W079 */// Suppress warning about redefiniton of `Promise`
var Promise = require( 'bluebird' );
var agent = require( 'superagent' );
var parseLinkHeader = require( 'parse-link-header' );

function resolveAsPromise( superagentReq ) {
return new Promise(function( resolve, reject ) {
superagentReq.end(function( err, res ) {
if ( err ) {
// If err.response is present, the request succeeded but we got an
// error from the server: surface & return that error
if ( err.response && err.response.error ) {
return reject( err.response.error );
}
// If err.response is not present, the request could not connect
return reject( err );
}
resolve( res );
});
});
}

/**
* Fetch the headers for a URL and inspect them to attempt to locate an API
* endpoint header. Return a promise that will be resolved with a string, or
* rejected if no such header can be located.
*
* @param {string} url An arbitrary URL within an API-enabled WordPress site
* @param {boolean} [useGET] Whether to use GET or HEAD to read the URL, to enable
* the method to upgrade to a full GET request if a HEAD
* request initially fails.
* @returns {Promise} A promise to the string containing the API endpoint URL
*/
function getAPIRootFromURL( url, useGET ) {

// If useGET is specified and truthy, .get the url; otherwise use .head
// because we only care about the HTTP headers, not the response body.
var request = useGET ? agent.get( url ) : agent.head( url );

return resolveAsPromise( request )
.catch(function( err ) {
// If this wasn't already a GET request, then on the hypothesis that an
// error arises from an unaccepted HEAD request, try again using GET
if ( ! useGET ) {
return getAPIRootFromURL( url, true );
}

// Otherwise re-throw the error
throw err;
});
}

function locateAPIRootHeader( response ) {
var rel = 'https://api.w.org/';

// Extract & parse the response link headers
var headers = parseLinkHeader( response.headers.link );
var apiHeader = headers && headers[ rel ];

if ( apiHeader && apiHeader.url ) {
return apiHeader.url;
}

throw new Error( 'No header link found with rel="https://api.w.org/"' );
}

/**
* Function to be called with the API url, once we have found one
*
* @param {String} linkUrl The href of the <link> pointing to the API root
* @return {Promise} Promise that resolves once the API root has been inspected
*/
function getRootResponseJSON( apiRootURL ) {
return resolveAsPromise( agent.get( apiRootURL ).set( 'Accept', 'application/json' ) )
.then(function( response ) {
return response.body;
});
}

module.exports = {
resolveAsPromise: resolveAsPromise,
getAPIRootFromURL: getAPIRootFromURL,
locateAPIRootHeader: locateAPIRootHeader,
getRootResponseJSON: getRootResponseJSON
};
7 changes: 2 additions & 5 deletions lib/constructors/wp-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,11 @@ function ensureFunction( fn ) {
*
* @param {Object} request A superagent request object
* @param {Function} callback A callback function (optional)
* @param {Function} transform A function to transform the result data (optional)
* @param {Function} transform A function to transform the result data
* @return {Promise} A promise to the superagent request
*/
function invokeAndPromisify( request, callback, transform ) {
callback = ensureFunction( callback );
transform = transform || identity;

return new Promise(function( resolve, reject ) {
// Fire off the result
Expand Down Expand Up @@ -591,9 +590,7 @@ WPRequest.prototype._renderPath = function() {
.sort(function( a, b ) {
var intA = parseInt( a, 10 );
var intB = parseInt( b, 10 );
if ( isNaN( intA ) && isNaN( intB ) ) {
return intA - intB;
}
return intA - intB;
})
.map(function( pathPartKey ) {
return pathParts[ pathPartKey ];
Expand Down
47 changes: 28 additions & 19 deletions lib/endpoint-factories.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,42 @@ var createEndpointRequest = require( './endpoint-request' ).create;
* provided namespace to define path value setters (and corresponding property
* validators) for all possible variants of each resource's API endpoints.
*
* @param {string} namespace The namespace string for these routes
* @param {object} routeDefinitions A dictionary of route definitions from buildRouteTree
* @param {string} namespace The namespace string for these routes
* @param {object} routesByNamespace A dictionary of namespace - route definition
* object pairs as generated from buildRouteTree,
* where each route definition object is a dictionary
* keyed by route definition strings
* @returns {object} A dictionary of endpoint request handler factories
*/
function generateEndpointFactories( namespace, routeDefinitions ) {
function generateEndpointFactories( routesByNamespace ) {

// Create
return Object.keys( routeDefinitions ).reduce(function( handlers, resource ) {
return Object.keys( routesByNamespace ).reduce(function( namespaces, namespace ) {
var routeDefinitions = routesByNamespace[ namespace ];

var handlerSpec = createResourceHandlerSpec( routeDefinitions[ resource ], resource );
// Create
namespaces[ namespace ] = Object.keys( routeDefinitions ).reduce(function( handlers, resource ) {

var EndpointRequest = createEndpointRequest( handlerSpec, resource, namespace );
var handlerSpec = createResourceHandlerSpec( routeDefinitions[ resource ], resource );

// "handler" object is now fully prepared; create the factory method that
// will instantiate and return a handler instance
handlers[ resource ] = function( options ) {
options = options || {};
options = extend( options, this._options );
return new EndpointRequest( options );
};
var EndpointRequest = createEndpointRequest( handlerSpec, resource, namespace );

// Expose the constructor as a property on the factory function, so that
// auto-generated endpoint request constructors may be further customized
// when needed
handlers[ resource ].Ctor = EndpointRequest;
// "handler" object is now fully prepared; create the factory method that
// will instantiate and return a handler instance
handlers[ resource ] = function( options ) {
options = options || {};
options = extend( options, this._options );
return new EndpointRequest( options );
};

return handlers;
// Expose the constructor as a property on the factory function, so that
// auto-generated endpoint request constructors may be further customized
// when needed
handlers[ resource ].Ctor = EndpointRequest;

return handlers;
}, {} );

return namespaces;
}, {} );
}

Expand Down
3 changes: 2 additions & 1 deletion lib/wp-register-route.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ function registerRoute( namespace, restBase, options ) {
// Go through the same steps used to bootstrap the client to parse the
// provided route out into a handler request method
var routeTree = buildRouteTree( routeObj );
var endpointFactories = generateEndpointFactories( namespace, routeTree[ namespace ] );
// Parse the mock route object into endpoint factories
var endpointFactories = generateEndpointFactories( routeTree )[ namespace ];
var EndpointRequest = endpointFactories[ Object.keys( endpointFactories )[ 0 ] ].Ctor;

if ( options && typeof options.mixins === 'object' ) {
Expand Down
32 changes: 17 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,32 @@
"node": ">= 0.10.0"
},
"dependencies": {
"bluebird": "^3.1.1",
"bluebird": "^3.4.1",
"li": "^1.0.1",
"lodash": "^2.4.2",
"node.extend": "^1.1.5",
"qs": "^6.0.2",
"route-parser": "^0.0.4",
"superagent": "^1.7.0"
"parse-link-header": "^0.4.1",
"qs": "^6.2.0",
"route-parser": "0.0.4",
"superagent": "^1.8.3"
},
"devDependencies": {
"chai": "^1.10.0",
"chai-as-promised": "^4.3.0",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-yuidoc": "^0.5.2",
"istanbul": "^0.3.22",
"jscs": "^3.0.4",
"chai": "^3.5.0",
"chai-as-promised": "^5.3.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-contrib-jshint": "^1.0.0",
"grunt-contrib-yuidoc": "^1.0.0",
"istanbul": "^0.4.4",
"jscs": "^3.0.5",
"jscs-stylish": "^0.3.1",
"jshint": "^2.9.2",
"jshint-stylish": "^2.2.0",
"load-grunt-tasks": "^0.6.0",
"load-grunt-tasks": "^3.5.0",
"minimist": "^1.2.0",
"mocha": "^1.21.5",
"sandboxed-module": "^1.0.3",
"sinon": "^1.17.2",
"mocha": "^2.5.3",
"sandboxed-module": "^2.0.3",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0"
}
}
Loading