From c8414f454a28c7e8df3846cc67914a76bb808ccf Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 11 Feb 2016 01:21:36 -0500 Subject: [PATCH 01/32] Initial work around parsing PCRE named groups in route strings --- lib/parse-route-string.js | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lib/parse-route-string.js diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js new file mode 100644 index 00000000..e72ac7fa --- /dev/null +++ b/lib/parse-route-string.js @@ -0,0 +1,80 @@ +'use strict'; +/** + * Take a WP route string (with PCRE named capture groups), such as + * @module parseRouteString + */ + +// All valid routes in API v2 beta 11 +var routes = { + '/': { namespace: '' }, + '/wp/v2': { namespace: 'wp/v2' }, + '/wp/v2/posts': { namespace: 'wp/v2' }, + '/wp/v2/posts/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/posts/(?P[\\d]+)/meta': { namespace: 'wp/v2' }, + '/wp/v2/posts/(?P[\\d]+)/meta/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/posts/(?P[\\d]+)/revisions': { namespace: 'wp/v2' }, + '/wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/pages': { namespace: 'wp/v2' }, + '/wp/v2/pages/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/pages/(?P[\\d]+)/meta': { namespace: 'wp/v2' }, + '/wp/v2/pages/(?P[\\d]+)/meta/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/pages/(?P[\\d]+)/revisions': { namespace: 'wp/v2' }, + '/wp/v2/pages/(?P[\\d]+)/revisions/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/media': { namespace: 'wp/v2' }, + '/wp/v2/media/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/types': { namespace: 'wp/v2' }, + '/wp/v2/types/(?P[\\w-]+)': { namespace: 'wp/v2' }, + '/wp/v2/statuses': { namespace: 'wp/v2' }, + '/wp/v2/statuses/(?P[\\w-]+)': { namespace: 'wp/v2' }, + '/wp/v2/taxonomies': { namespace: 'wp/v2' }, + '/wp/v2/taxonomies/(?P[\\w-]+)': { namespace: 'wp/v2' }, + '/wp/v2/categories': { namespace: 'wp/v2' }, + '/wp/v2/categories/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/tags': { namespace: 'wp/v2' }, + '/wp/v2/tags/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/users': { namespace: 'wp/v2' }, + '/wp/v2/users/(?P[\\d]+)': { namespace: 'wp/v2' }, + '/wp/v2/users/me': { namespace: 'wp/v2' }, + '/wp/v2/comments': { namespace: 'wp/v2' }, + '/wp/v2/comments/(?P[\\d]+)': { namespace: 'wp/v2' } +}; + +// Regular Expression to identify a capture group in PCRE formats `(?regex)`, +// `(?'name'regex)` or `(?Pregex)` (see regular-expressions.info/refext.html), +// built as a string to enable more detailed annotation. +var namedGroupRegexp = new RegExp([ + // Capture group start + '\\(\?', + // Capture group name begins either `P<`, `<` or `'` + '(?:P<|<|\')', + // Everything up to the next `>`` or `'` (depending) will be the capture group name + '([^>\']+)', + // Capture group end + '[>\']', + // Get everything up to the end of the capture group: this is the RegExp used + // when matching URLs to this route, which we can use for validation purposes. + '([^\\)]+)', + // Capture group end + '\\)' +].join( '' ) ); + +Object.keys( routes ).forEach(function( route ) { + console.log( route.match( namedGroupRegexp ) ); +}); + +var routesByNamespace = Object.keys( routes ).reduce(function( nsGroups, route ) { + var nsForRoute = routes[ route ].namespace; + if ( ! nsGroups[ nsForRoute ] ) { + nsGroups[ nsForRoute ] = []; + } + nsGroups[ nsForRoute ].push( routes[ route ] ); +}); + +Object.keys( routes ).forEach(function( route ) { + // All routes will begin with + var nsForRoute = routes[ route ].ns; + // First of all, strip all initial slashes + route = route.replace( /^\//, '' ); + // Next, remove the namespace, if it is currently prefixed + route = route.replace( ns, '' ); +}) From 2d7b5d437e5609bdf1a464612dfc810a84807cb0 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 11 Feb 2016 01:29:33 -0500 Subject: [PATCH 02/32] Properly group route strings under their corresponding namespaces --- lib/parse-route-string.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index e72ac7fa..eb363279 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -58,18 +58,22 @@ var namedGroupRegexp = new RegExp([ '\\)' ].join( '' ) ); -Object.keys( routes ).forEach(function( route ) { - console.log( route.match( namedGroupRegexp ) ); -}); - var routesByNamespace = Object.keys( routes ).reduce(function( nsGroups, route ) { var nsForRoute = routes[ route ].namespace; - if ( ! nsGroups[ nsForRoute ] ) { + if ( nsForRoute && '/' + nsForRoute !== route && ! nsGroups[ nsForRoute ] ) { nsGroups[ nsForRoute ] = []; } - nsGroups[ nsForRoute ].push( routes[ route ] ); -}); + // Don't add the namespace root to its own group + if ( '/' + nsForRoute !== route ) { + // Add this route string to its namespace group, stripping the namespace from + // the route string + nsGroups[ nsForRoute ].push( route.replace( '/' + nsForRoute + '/', '' ) ); + } + return nsGroups; +}, {} ); +console.log( routesByNamespace ); +/* Object.keys( routes ).forEach(function( route ) { // All routes will begin with var nsForRoute = routes[ route ].ns; @@ -78,3 +82,4 @@ Object.keys( routes ).forEach(function( route ) { // Next, remove the namespace, if it is currently prefixed route = route.replace( ns, '' ); }) +*/ From 44a0e9afa51ca88b802b434484268fdc5c95fc3f Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 11 Feb 2016 02:17:31 -0500 Subject: [PATCH 03/32] Begin fleshing out, but then abort, the first draft solution --- lib/parse-route-string.js | 165 +++++++++++++++++++++++++++++++++++--- 1 file changed, 154 insertions(+), 11 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index eb363279..ae2b8a17 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -44,7 +44,7 @@ var routes = { // built as a string to enable more detailed annotation. var namedGroupRegexp = new RegExp([ // Capture group start - '\\(\?', + '\\(\\?', // Capture group name begins either `P<`, `<` or `'` '(?:P<|<|\')', // Everything up to the next `>`` or `'` (depending) will be the capture group name @@ -58,21 +58,164 @@ var namedGroupRegexp = new RegExp([ '\\)' ].join( '' ) ); -var routesByNamespace = Object.keys( routes ).reduce(function( nsGroups, route ) { +var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route ) { var nsForRoute = routes[ route ].namespace; - if ( nsForRoute && '/' + nsForRoute !== route && ! nsGroups[ nsForRoute ] ) { - nsGroups[ nsForRoute ] = []; + // Do not make a namespace group for the API root + if ( ! nsForRoute ) { + return namespaces; } - // Don't add the namespace root to its own group - if ( '/' + nsForRoute !== route ) { - // Add this route string to its namespace group, stripping the namespace from - // the route string - nsGroups[ nsForRoute ].push( route.replace( '/' + nsForRoute + '/', '' ) ); + + // Do not add the namespace root to its own group + if ( '/' + nsForRoute === route ) { + return namespaces; + } + + // Ensure that the namespace object for this namespace exists + if ( ! namespaces[ nsForRoute ] ) { + namespaces[ nsForRoute ] = {}; } - return nsGroups; + + // Get a local reference to namespace object + var ns = namespaces[ nsForRoute ]; + + // Strip the namespace from the route string (all routes should have the + // format `/namespace/other/stuff`) @TODO: Validate this assumption + var routeString = route.replace( '/' + nsForRoute + '/', '' ); + + // If no route string, carry on + if ( ! routeString ) { return namespaces; } + + var routeComponents = routeString.split( '/' ); + + // If no components, carry on + if ( ! routeComponents.length ) { return namespaces; } + + // The first element of the route tells us what type of resource this route + // is for, e.g. "posts" or "comments": we build one handler per resource + // type, so we group like resource paths together. + var resource = routeComponents.shift(); + + // @TODO: This code above currently precludes baseless routes, e.g. + // myplugin/v2/(?P\w+) -- should those be supported? + + // Create an array to represent this resource, and ensure it is assigned + // to the namespace object. The array will structure the "levels" (path + // components and subresource types) of this resource's endpoint handler. + var levels = ns[ resource ] || []; + ns[ resource ] = levels; + + routeComponents.forEach(function( component, idx ) { + if ( ! levels[ idx ] ) { + levels.push({ + setters: [] + }); + } + + // Check to see if this component is a dynamic URL segment (i.e. defined by + // a named capture group regular expression). namedGroup will be null if + // the regexp does not match, or else an array defining the RegExp match, e.g. + // [ + // 'P[\\d]+)', + // 'id', // Name of the group + // '[\\d]+', // regular expression for this URL segment's contents + // index: 15, + // input: '/wp/v2/posts/(?P[\\d]+)' + // ] + var namedGroup = component.match( namedGroupRegexp ); + + // Branch here: if we're NOT dealing with a dynamic URL component, + if ( ! namedGroup ) { + // then add a simple-case setter for whatever static string we're + // dealing with here + levels[ idx ].setters.push({ + name: component, + validate: function( input ) { + return input === component; + } + }); + + // and move along. + return; + } + + // The approach below will not work. The reason it will not work is this: + // + // /wp/v2/posts/(?P[\\d]+) + // /wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+) + // + // If we define a `.id()` setter to set the first "level" of the URL we are + // building, then `id` is a taken name. The approach that we begin to build + // out below has no way of tracking which setters are still "available," and + // when we try to make a setter for the revision ID, it will will either + // overwrite the post ID setter or else be dropped (neither of which would be + // considered a successful outcome). + // + // We either need to construct a tree of these resources, a la + // + // - posts + // - id + // - revisions + // - id + // - meta + // - id + // + // or else keep more of a sense of what "level" we are dealing with, and work + // inwards from leaf nodes so that we can bind a `.revisions()` method that + // would return a collection if called with no arguments, or set `.id` with + // a numeric argument; we can't be that smart unless we know where our leaf + // nodes are, and which nodes they come off of. + + // // // If we ARE dealing with a named capture group, see whether the regexp + // // // it is capturing is already accounted for: this is done because multiple + // // // route strings may treat the same URL component with a different name, e.g. + // // // '/wp/v2/posts/(?P[\\d]+)' vs '/wp/v2/posts/(?P[\\d]+)/revisions' + // // var reHandled = levels[ idx ].setters.reduce(function( isHandled, setter ) { + // // if ( setter.re && setter.re === namedGroup[ 2 ] ) { + // // return true; + // // } + // // return isHandled; + // // }, false ); + // // var validationRegExp = new RegExp( '^' + namedGroup[ 2 ] + '$' ); + // // if ( ! reHandled ) { + // // levels[ idx ].setters.push({ + // // name: namedGroup[ 1 ], + // // re: namedGroup[ 2 ], + // // validate: function( input ) { + // // return validationRegExp.test( input ); + // // } + // // }); + // // } + // // If we ARE dealing with a named capture group, see whether there is already + // // a handler with the provided name: we only want to bind one setter for "id" + // // even if multiple routes specify this level as "id". + // // it is capturing is already accounted for: this is done because multiple + // // route strings may treat the same URL component with a different name, e.g. + // // '/wp/v2/posts/(?P[\\d]+)' vs '/wp/v2/posts/(?P[\\d]+)/revisions' + // var reHandled = levels[ idx ].setters.reduce(function( isHandled, setter ) { + // if ( setter.re && setter.re === namedGroup[ 2 ] ) { + // return true; + // } + // return isHandled; + // }, false ); + // var validationRegExp = new RegExp( '^' + namedGroup[ 2 ] + '$' ); + // if ( ! reHandled ) { + // levels[ idx ].setters.push({ + // name: namedGroup[ 1 ], + // re: namedGroup[ 2 ], + // validate: function( input ) { + // return validationRegExp.test( input ); + // } + // }); + // } + }); + + namespaces[ nsForRoute ].routes = namespaces[ nsForRoute ].routes || []; + namespaces[ nsForRoute ].routes.push( routeString ); + + return namespaces; }, {} ); -console.log( routesByNamespace ); +console.log( routesByNamespace['wp/v2'] ); /* Object.keys( routes ).forEach(function( route ) { // All routes will begin with From 78c0b7e05c7dc58b167870f5a35efdd0dcfeccba Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 11 Feb 2016 02:33:19 -0500 Subject: [PATCH 04/32] Successfully parse wp/v2 routes into tree structure --- lib/parse-route-string.js | 124 +++++++++----------------------------- 1 file changed, 30 insertions(+), 94 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index ae2b8a17..422f4ef6 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -3,6 +3,7 @@ * Take a WP route string (with PCRE named capture groups), such as * @module parseRouteString */ +var util = require( 'util' ); // All valid routes in API v2 beta 11 var routes = { @@ -101,16 +102,10 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route // Create an array to represent this resource, and ensure it is assigned // to the namespace object. The array will structure the "levels" (path // components and subresource types) of this resource's endpoint handler. - var levels = ns[ resource ] || []; + var levels = ns[ resource ] || {}; ns[ resource ] = levels; - routeComponents.forEach(function( component, idx ) { - if ( ! levels[ idx ] ) { - levels.push({ - setters: [] - }); - } - + routeComponents.reduce(function( currentLevel, component, idx ) { // Check to see if this component is a dynamic URL segment (i.e. defined by // a named capture group regular expression). namedGroup will be null if // the regexp does not match, or else an array defining the RegExp match, e.g. @@ -123,99 +118,40 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route // ] var namedGroup = component.match( namedGroupRegexp ); - // Branch here: if we're NOT dealing with a dynamic URL component, + // If this component does not represent a dynamic URL segment either add + // it as a node on this level of the endpoint tree, or simply return the + // node if it already exists. if ( ! namedGroup ) { - // then add a simple-case setter for whatever static string we're - // dealing with here - levels[ idx ].setters.push({ - name: component, - validate: function( input ) { - return input === component; - } - }); - - // and move along. - return; + currentLevel[ component ] = currentLevel[ component ] || {}; + + // Return this component as the new "level" + return currentLevel[ component ]; } - // The approach below will not work. The reason it will not work is this: - // - // /wp/v2/posts/(?P[\\d]+) - // /wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+) - // - // If we define a `.id()` setter to set the first "level" of the URL we are - // building, then `id` is a taken name. The approach that we begin to build - // out below has no way of tracking which setters are still "available," and - // when we try to make a setter for the revision ID, it will will either - // overwrite the post ID setter or else be dropped (neither of which would be - // considered a successful outcome). - // - // We either need to construct a tree of these resources, a la - // - // - posts - // - id - // - revisions - // - id - // - meta - // - id - // - // or else keep more of a sense of what "level" we are dealing with, and work - // inwards from leaf nodes so that we can bind a `.revisions()` method that - // would return a collection if called with no arguments, or set `.id` with - // a numeric argument; we can't be that smart unless we know where our leaf - // nodes are, and which nodes they come off of. - - // // // If we ARE dealing with a named capture group, see whether the regexp - // // // it is capturing is already accounted for: this is done because multiple - // // // route strings may treat the same URL component with a different name, e.g. - // // // '/wp/v2/posts/(?P[\\d]+)' vs '/wp/v2/posts/(?P[\\d]+)/revisions' - // // var reHandled = levels[ idx ].setters.reduce(function( isHandled, setter ) { - // // if ( setter.re && setter.re === namedGroup[ 2 ] ) { - // // return true; - // // } - // // return isHandled; - // // }, false ); - // // var validationRegExp = new RegExp( '^' + namedGroup[ 2 ] + '$' ); - // // if ( ! reHandled ) { - // // levels[ idx ].setters.push({ - // // name: namedGroup[ 1 ], - // // re: namedGroup[ 2 ], - // // validate: function( input ) { - // // return validationRegExp.test( input ); - // // } - // // }); - // // } - // // If we ARE dealing with a named capture group, see whether there is already - // // a handler with the provided name: we only want to bind one setter for "id" - // // even if multiple routes specify this level as "id". - // // it is capturing is already accounted for: this is done because multiple - // // route strings may treat the same URL component with a different name, e.g. - // // '/wp/v2/posts/(?P[\\d]+)' vs '/wp/v2/posts/(?P[\\d]+)/revisions' - // var reHandled = levels[ idx ].setters.reduce(function( isHandled, setter ) { - // if ( setter.re && setter.re === namedGroup[ 2 ] ) { - // return true; - // } - // return isHandled; - // }, false ); - // var validationRegExp = new RegExp( '^' + namedGroup[ 2 ] + '$' ); - // if ( ! reHandled ) { - // levels[ idx ].setters.push({ - // name: namedGroup[ 1 ], - // re: namedGroup[ 2 ], - // validate: function( input ) { - // return validationRegExp.test( input ); - // } - // }); - // } - }); - - namespaces[ nsForRoute ].routes = namespaces[ nsForRoute ].routes || []; - namespaces[ nsForRoute ].routes.push( routeString ); + // At this point we know that this component represents a dynamic URL + // segment. Pull out references to the relevant components of the named + // capture group, for utility's sake: + var groupName = namedGroup[ 1 ]; + var groupPattern = namedGroup[ 2 ]; + + // The same pattern may be assigned different names in different endpoints, + // so determine node existence by RE pattern and not by name: + currentLevel[ groupPattern ] = currentLevel[ groupPattern ] || {}; + + // Return this component as the new "level" + return currentLevel[ groupPattern ]; + }, levels ); + + // namespaces[ nsForRoute ] = levels; + // namespaces[ nsForRoute ].routes.push( routeString ); return namespaces; }, {} ); -console.log( routesByNamespace['wp/v2'] ); +console.log( util.inspect( routesByNamespace, { + depth: null +}) ); + /* Object.keys( routes ).forEach(function( route ) { // All routes will begin with From df50165745837fcc5fa2d2d8faff443640a9d4fe Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 11 Feb 2016 02:48:09 -0500 Subject: [PATCH 05/32] DRY up tree generation code and structure it with children objects --- lib/parse-route-string.js | 47 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index 422f4ef6..ff2b2fb6 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -105,9 +105,9 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route var levels = ns[ resource ] || {}; ns[ resource ] = levels; - routeComponents.reduce(function( currentLevel, component, idx ) { + routeComponents.reduce(function( currentLevel, component, idx, components ) { // Check to see if this component is a dynamic URL segment (i.e. defined by - // a named capture group regular expression). namedGroup will be null if + // a named capture group regular expression). namedGroup will be `null` if // the regexp does not match, or else an array defining the RegExp match, e.g. // [ // 'P[\\d]+)', @@ -117,29 +117,30 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route // input: '/wp/v2/posts/(?P[\\d]+)' // ] var namedGroup = component.match( namedGroupRegexp ); - - // If this component does not represent a dynamic URL segment either add - // it as a node on this level of the endpoint tree, or simply return the - // node if it already exists. - if ( ! namedGroup ) { - currentLevel[ component ] = currentLevel[ component ] || {}; - - // Return this component as the new "level" - return currentLevel[ component ]; + // Pull out references to the relevant indices of the match, for utility: + // `null` checking is necessary in case the component did not match the RE, + // hence the `namedGroup &&`. + var groupName = namedGroup && namedGroup[ 1 ]; + var groupPattern = namedGroup && namedGroup[ 2 ]; + + // When branching based on a dynamic capture group we used the group's RE + // pattern as the unique identifier: this is done because the same group + // could be assigned different names in different endpoint handlers, e.g. + // "id" for posts/:id vs "parent_id" for posts/:parent_id/revisions. + var nextLevel = namedGroup ? groupPattern : component; + + // Check whether we have a preexisting node at this level of the tree, and + // create a new level if not. + currentLevel[ nextLevel ] = currentLevel[ nextLevel ] || {}; + + // Check to see whether to expect more nodes within this branch of the tree, + // and create a "children" object to hold those nodes if necessary + if ( components[ idx + 1 ] ) { + currentLevel[ nextLevel ].children = currentLevel[ nextLevel ].children || {}; } - // At this point we know that this component represents a dynamic URL - // segment. Pull out references to the relevant components of the named - // capture group, for utility's sake: - var groupName = namedGroup[ 1 ]; - var groupPattern = namedGroup[ 2 ]; - - // The same pattern may be assigned different names in different endpoints, - // so determine node existence by RE pattern and not by name: - currentLevel[ groupPattern ] = currentLevel[ groupPattern ] || {}; - - // Return this component as the new "level" - return currentLevel[ groupPattern ]; + // Return the child node object as the new "level" + return currentLevel[ nextLevel ].children; }, levels ); // namespaces[ nsForRoute ] = levels; From a830aef5712fc1ba98131f4846455bc924996ee2 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 11 Feb 2016 03:24:52 -0500 Subject: [PATCH 06/32] Augment node tree with info about component names and validators --- lib/parse-route-string.js | 60 +++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index ff2b2fb6..51e23d10 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -4,6 +4,11 @@ * @module parseRouteString */ var util = require( 'util' ); +function logFull( obj ) { + console.log( util.inspect( obj, { + depth: null + }) ); +} // All valid routes in API v2 beta 11 var routes = { @@ -105,7 +110,7 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route var levels = ns[ resource ] || {}; ns[ resource ] = levels; - routeComponents.reduce(function( currentLevel, component, idx, components ) { + routeComponents.reduce(function( parentLevel, component, idx, components ) { // Check to see if this component is a dynamic URL segment (i.e. defined by // a named capture group regular expression). namedGroup will be `null` if // the regexp does not match, or else an array defining the RegExp match, e.g. @@ -127,20 +132,44 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route // pattern as the unique identifier: this is done because the same group // could be assigned different names in different endpoint handlers, e.g. // "id" for posts/:id vs "parent_id" for posts/:parent_id/revisions. - var nextLevel = namedGroup ? groupPattern : component; + var levelKey = namedGroup ? groupPattern : component; + + // Level name, on the other hand, would take its value from the group's name + var levelName = namedGroup ? groupName : component; // Check whether we have a preexisting node at this level of the tree, and - // create a new level if not. - currentLevel[ nextLevel ] = currentLevel[ nextLevel ] || {}; + // create a new level object if not + var currentLevel = parentLevel[ levelKey ] || { + names: [] + }; + + // A level's "name" corresponds to the list of strings which could describe + // an endpoint's component setter functions: "id", "revisions", etc. + if ( currentLevel.names.indexOf( levelName ) < 0 ) { + currentLevel.names.push( levelName ); + } + + // A level's validate method is called to check whether a value being set + // on the request URL is of the proper type for the location in which it + // is specified. If a group pattern was found, the validator checks whether + // the input string exactly matches the group pattern. + var groupPatternRE = groupPattern && new RegExp( '^' + groupPattern + '$' ); + + // Only one validate function is maintained for each node, because each node + // is defined either by a string literal or by a specific regular expression. + currentLevel.validate = function( input ) { + return groupPatternRE ? groupPatternRE.test( input ) : input === component; + }; // Check to see whether to expect more nodes within this branch of the tree, // and create a "children" object to hold those nodes if necessary if ( components[ idx + 1 ] ) { - currentLevel[ nextLevel ].children = currentLevel[ nextLevel ].children || {}; + currentLevel.children = currentLevel.children || {}; } // Return the child node object as the new "level" - return currentLevel[ nextLevel ].children; + parentLevel[ levelKey ] = currentLevel; + return currentLevel.children; }, levels ); // namespaces[ nsForRoute ] = levels; @@ -149,9 +178,22 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route return namespaces; }, {} ); -console.log( util.inspect( routesByNamespace, { - depth: null -}) ); +// Now that our namespace and levels object has been defined, recurse through +// the node tree representing all possible routes within that namespace to +// define the path value setters and corresponding validators for all possible +// variants of each resource's API endpoints +Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { + var nsRoutes = routesByNamespace[ namespace ]; + var endpointHandlers = Object.keys( nsRoutes ).reduce(function( handlers, resource ) { + var levels = nsRoutes[ resource ]; + console.log( resource ); + logFull( levels ); + }, {} ) +}); + +// console.log( util.inspect( routesByNamespace, { +// depth: null +// }) ); /* Object.keys( routes ).forEach(function( route ) { From 5fbc77391496321475233df6371c0525fb19e39d Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 11 Feb 2016 10:39:29 -0500 Subject: [PATCH 07/32] Augment route definition tree with more metadata from schema --- lib/endpoint-response.json | 1 + lib/parse-route-string.js | 55 +++++++++++--------------------------- 2 files changed, 16 insertions(+), 40 deletions(-) create mode 100644 lib/endpoint-response.json diff --git a/lib/endpoint-response.json b/lib/endpoint-response.json new file mode 100644 index 00000000..a638397b --- /dev/null +++ b/lib/endpoint-response.json @@ -0,0 +1 @@ +{"name":"WP-API Testbed","description":"Just another WordPress site","url":"http:\/\/wpapi.loc\/wp","namespaces":["wp\/v2","oembed\/1.0"],"authentication":[],"routes":{"\/":{"namespace":"","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view"}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/"}},"\/wp\/v2":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"namespace":{"required":false,"default":"wp\/v2"},"context":{"required":false,"default":"view"}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2"}},"\/wp\/v2\/posts":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug"],"description":"Sort collection by object attribute."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"publish","description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."}}},{"methods":["POST"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"format":{"required":false,"enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"]},"sticky":{"required":false},"categories":{"required":false},"tags":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"format":{"description":"The format for the object.","type":"string","enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"],"context":["view","edit"]},"sticky":{"description":"Whether or not the object should be treated as sticky.","type":"boolean","context":["view","edit"]},"categories":{"description":"The terms assigned to the object in the category taxonomy.","type":"array","context":["view","edit"]},"tags":{"description":"The terms assigned to the object in the post_tag taxonomy.","type":"array","context":["view","edit"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/posts"}},"\/wp\/v2\/posts\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"format":{"required":false,"enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"]},"sticky":{"required":false},"categories":{"required":false},"tags":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"format":{"description":"The format for the object.","type":"string","enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"],"context":["view","edit"]},"sticky":{"description":"Whether or not the object should be treated as sticky.","type":"boolean","context":["view","edit"]},"categories":{"description":"The terms assigned to the object in the category taxonomy.","type":"array","context":["view","edit"]},"tags":{"description":"The terms assigned to the object in the post_tag taxonomy.","type":"array","context":["view","edit"]}}}},"\/wp\/v2\/posts\/(?P[\\d]+)\/revisions":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/posts\/(?P[\\d]+)\/revisions\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["DELETE"],"args":[]}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/pages":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"menu_order":{"required":false,"description":"Limit result set to resources with a specific menu_order value."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug","menu_order"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to those of particular parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Limit result set to all items except those of a particular parent id."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"publish","description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."}}},{"methods":["POST"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"parent":{"required":false},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"menu_order":{"required":false},"template":{"required":false,"enum":[]}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit"]},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"menu_order":{"description":"The order of the object in relation to other object of its type.","type":"integer","context":["view","edit"]},"template":{"description":"The theme file to use to display the object.","type":"string","enum":[],"context":["view","edit"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/pages"}},"\/wp\/v2\/pages\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"parent":{"required":false},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"menu_order":{"required":false},"template":{"required":false,"enum":[]}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit"]},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"menu_order":{"description":"The order of the object in relation to other object of its type.","type":"integer","context":["view","edit"]},"template":{"description":"The theme file to use to display the object.","type":"string","enum":[],"context":["view","edit"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/revisions":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/revisions\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["DELETE"],"args":[]}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/media":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to those of particular parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Limit result set to all items except those of a particular parent id."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"inherit","enum":["inherit","private","trash"],"description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."},"media_type":{"required":false,"enum":["image","video","text","application","audio"],"description":"Limit result set to attachments of a particular media type."},"mime_type":{"required":false,"description":"Limit result set to attachments of a particular mime type."}}},{"methods":["POST"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"author":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"alt_text":{"required":false},"caption":{"required":false},"description":{"required":false},"post":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"attachment","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"alt_text":{"description":"Alternative text to display when resource is not displayed.","type":"string","context":["view","edit","embed"]},"caption":{"description":"The caption for the resource.","type":"string","context":["view","edit"]},"description":{"description":"The description for the resource.","type":"string","context":["view","edit"]},"media_type":{"description":"Type of resource.","type":"string","enum":["image","file"],"context":["view","edit","embed"],"readonly":true},"mime_type":{"description":"Mime type of resource.","type":"string","context":["view","edit","embed"],"readonly":true},"media_details":{"description":"Details about the resource file, specific to its type.","type":"object","context":["view","edit","embed"],"readonly":true},"post":{"description":"The id for the associated post of the resource.","type":"integer","context":["view","edit"]},"source_url":{"description":"URL to the original resource file.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/media"}},"\/wp\/v2\/media\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"author":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"alt_text":{"required":false},"caption":{"required":false},"description":{"required":false},"post":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"attachment","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"alt_text":{"description":"Alternative text to display when resource is not displayed.","type":"string","context":["view","edit","embed"]},"caption":{"description":"The caption for the resource.","type":"string","context":["view","edit"]},"description":{"description":"The description for the resource.","type":"string","context":["view","edit"]},"media_type":{"description":"Type of resource.","type":"string","enum":["image","file"],"context":["view","edit","embed"],"readonly":true},"mime_type":{"description":"Mime type of resource.","type":"string","context":["view","edit","embed"],"readonly":true},"media_details":{"description":"Details about the resource file, specific to its type.","type":"object","context":["view","edit","embed"],"readonly":true},"post":{"description":"The id for the associated post of the resource.","type":"integer","context":["view","edit"]},"source_url":{"description":"URL to the original resource file.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true}}}},"\/wp\/v2\/types":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"type","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/types"}},"\/wp\/v2\/types\/(?P[\\w-]+)":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"type","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true}}}},"\/wp\/v2\/statuses":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"status","type":"object","properties":{"name":{"description":"The title for the resource.","type":"string","context":["embed","view","edit"],"readonly":true},"private":{"description":"Whether posts with this resource should be private.","type":"boolean","context":["edit"],"readonly":true},"protected":{"description":"Whether posts with this resource should be protected.","type":"boolean","context":["edit"],"readonly":true},"public":{"description":"Whether posts of this resource should be shown in the front end of the site.","type":"boolean","context":["view","edit"],"readonly":true},"queryable":{"description":"Whether posts with this resource should be publicly-queryable.","type":"boolean","context":["view","edit"],"readonly":true},"show_in_list":{"description":"Whether to include posts in the edit listing for their post type.","type":"boolean","context":["edit"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/statuses"}},"\/wp\/v2\/statuses\/(?P[\\w-]+)":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"status","type":"object","properties":{"name":{"description":"The title for the resource.","type":"string","context":["embed","view","edit"],"readonly":true},"private":{"description":"Whether posts with this resource should be private.","type":"boolean","context":["edit"],"readonly":true},"protected":{"description":"Whether posts with this resource should be protected.","type":"boolean","context":["edit"],"readonly":true},"public":{"description":"Whether posts of this resource should be shown in the front end of the site.","type":"boolean","context":["view","edit"],"readonly":true},"queryable":{"description":"Whether posts with this resource should be publicly-queryable.","type":"boolean","context":["view","edit"],"readonly":true},"show_in_list":{"description":"Whether to include posts in the edit listing for their post type.","type":"boolean","context":["edit"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"],"readonly":true}}}},"\/wp\/v2\/taxonomies":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"type":{"required":false,"description":"Limit results to resources associated with a specific post type."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"taxonomy","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"show_cloud":{"description":"Whether or not the term cloud should be displayed.","type":"boolean","context":["edit"],"readonly":true},"types":{"description":"Types associated with resource.","type":"array","context":["view","edit"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/taxonomies"}},"\/wp\/v2\/taxonomies\/(?P[\\w-]+)":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"taxonomy","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"show_cloud":{"description":"Whether or not the term cloud should be displayed.","type":"boolean","context":["edit"],"readonly":true},"types":{"description":"Types associated with resource.","type":"array","context":["view","edit"],"readonly":true}}}},"\/wp\/v2\/categories":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","slug","term_group","description","count"],"description":"Sort collection by resource attribute."},"hide_empty":{"required":false,"default":false,"description":"Whether to hide resources not assigned to any posts."},"parent":{"required":false,"description":"Limit result set to resources assigned to a specific parent."},"post":{"required":false,"description":"Limit result set to resources assigned to a specific post."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"description":{"required":false},"name":{"required":true},"slug":{"required":false},"parent":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"category","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true},"parent":{"description":"The id for the parent of the resource.","type":"integer","context":["view","edit"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/categories"}},"\/wp\/v2\/categories\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"description":{"required":false},"name":{"required":false},"slug":{"required":false},"parent":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"category","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true},"parent":{"description":"The id for the parent of the resource.","type":"integer","context":["view","edit"]}}}},"\/wp\/v2\/tags":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","slug","term_group","description","count"],"description":"Sort collection by resource attribute."},"hide_empty":{"required":false,"default":false,"description":"Whether to hide resources not assigned to any posts."},"post":{"required":false,"description":"Limit result set to resources assigned to a specific post."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"description":{"required":false},"name":{"required":true},"slug":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"tag","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/tags"}},"\/wp\/v2\/tags\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"description":{"required":false},"name":{"required":false},"slug":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"tag","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true}}}},"\/wp\/v2\/users":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","registered_date"],"description":"Sort collection by object attribute."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"username":{"required":true},"name":{"required":false},"first_name":{"required":false},"last_name":{"required":false},"email":{"required":true},"url":{"required":false},"description":{"required":false},"nickname":{"required":false},"slug":{"required":false},"roles":{"required":false},"capabilities":{"required":false},"password":{"required":true}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"user","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["embed","view","edit"],"readonly":true},"username":{"description":"Login name for the resource.","type":"string","context":["edit"],"required":true},"name":{"description":"Display name for the resource.","type":"string","context":["embed","view","edit"]},"first_name":{"description":"First name for the resource.","type":"string","context":["edit"]},"last_name":{"description":"Last name for the resource.","type":"string","context":["edit"]},"email":{"description":"The email address for the resource.","type":"string","format":"email","context":["edit"],"required":true},"url":{"description":"URL of the resource.","type":"string","format":"uri","context":["embed","view","edit"]},"description":{"description":"Description of the resource.","type":"string","context":["embed","view","edit"]},"link":{"description":"Author URL to the resource.","type":"string","format":"uri","context":["embed","view","edit"],"readonly":true},"nickname":{"description":"The nickname for the resource.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"]},"registered_date":{"description":"Registration date for the resource.","type":"date-time","context":["edit"],"readonly":true},"roles":{"description":"Roles assigned to the resource.","type":"array","context":["edit"]},"capabilities":{"description":"All capabilities assigned to the resource.","type":"object","context":["edit"]},"extra_capabilities":{"description":"Any extra capabilities assigned to the resource.","type":"object","context":["edit"],"readonly":true},"avatar_urls":{"description":"Avatar URLs for the resource.","type":"object","context":["embed","view","edit"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/users"}},"\/wp\/v2\/users\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"username":{"required":false},"name":{"required":false},"first_name":{"required":false},"last_name":{"required":false},"email":{"required":false},"url":{"required":false},"description":{"required":false},"nickname":{"required":false},"slug":{"required":false},"roles":{"required":false},"capabilities":{"required":false},"password":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."},"reassign":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"user","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["embed","view","edit"],"readonly":true},"username":{"description":"Login name for the resource.","type":"string","context":["edit"],"required":true},"name":{"description":"Display name for the resource.","type":"string","context":["embed","view","edit"]},"first_name":{"description":"First name for the resource.","type":"string","context":["edit"]},"last_name":{"description":"Last name for the resource.","type":"string","context":["edit"]},"email":{"description":"The email address for the resource.","type":"string","format":"email","context":["edit"],"required":true},"url":{"description":"URL of the resource.","type":"string","format":"uri","context":["embed","view","edit"]},"description":{"description":"Description of the resource.","type":"string","context":["embed","view","edit"]},"link":{"description":"Author URL to the resource.","type":"string","format":"uri","context":["embed","view","edit"],"readonly":true},"nickname":{"description":"The nickname for the resource.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"]},"registered_date":{"description":"Registration date for the resource.","type":"date-time","context":["edit"],"readonly":true},"roles":{"description":"Roles assigned to the resource.","type":"array","context":["edit"]},"capabilities":{"description":"All capabilities assigned to the resource.","type":"object","context":["edit"]},"extra_capabilities":{"description":"Any extra capabilities assigned to the resource.","type":"object","context":["edit"],"readonly":true},"avatar_urls":{"description":"Avatar URLs for the resource.","type":"object","context":["embed","view","edit"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}}}}},"\/wp\/v2\/users\/me":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/users\/me"}},"\/wp\/v2\/comments":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"description":"Limit result set to comments assigned to specific user ids. Requires authorization."},"author_exclude":{"required":false,"description":"Ensure result set excludes comments assigned to specific user ids. Requires authorization."},"author_email":{"required":false,"description":"Limit result set to that from a specific author email. Requires authorization."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"karma":{"required":false,"description":"Limit result set to that of a particular comment karma. Requires authorization."},"offset":{"required":false,"description":"Offset the result set by a specific number of comments."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date_gmt","enum":["date","date_gmt","id","include","post","parent","type"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to resources of specific parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific parent ids."},"post":{"required":false,"default":[],"description":"Limit result set to resources assigned to specific post ids."},"status":{"required":false,"default":"approve","description":"Limit result set to comments assigned a specific status. Requires authorization."},"type":{"required":false,"default":"comment","description":"Limit result set to comments assigned a specific type. Requires authorization."}}},{"methods":["POST"],"args":{"author":{"required":false},"author_email":{"required":false},"author_name":{"required":false,"default":""},"author_url":{"required":false},"content":{"required":false,"default":""},"date":{"required":false},"date_gmt":{"required":false},"karma":{"required":false},"parent":{"required":false,"default":0},"post":{"required":false,"default":0},"status":{"required":false},"type":{"required":false,"default":""}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"comment","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"author":{"description":"The id of the user object, if author was a user.","type":"integer","context":["view","edit","embed"]},"author_avatar_urls":{"description":"Avatar URLs for the object author.","type":"object","context":["view","edit","embed"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}},"author_email":{"description":"Email address for the object author.","type":"string","format":"email","context":["edit"]},"author_ip":{"description":"IP address for the object author.","type":"string","context":["edit"],"readonly":true},"author_name":{"description":"Display name for the object author.","type":"string","context":["view","edit","embed"]},"author_url":{"description":"URL for the object author.","type":"string","format":"uri","context":["view","edit","embed"]},"author_user_agent":{"description":"User agent for the object author.","type":"string","context":["edit"],"readonly":true},"content":{"description":"The content for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published as GMT.","type":"string","format":"date-time","context":["view","edit"]},"karma":{"description":"Karma for the object.","type":"integer","context":["edit"]},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"post":{"description":"The id of the associated post object.","type":"integer","context":["view","edit"]},"status":{"description":"State of the object.","type":"string","context":["view","edit"]},"type":{"description":"Type of Comment for the object.","type":"string","context":["view","edit","embed"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/comments"}},"\/wp\/v2\/comments\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"author":{"required":false},"author_email":{"required":false},"author_name":{"required":false},"author_url":{"required":false},"content":{"required":false},"date":{"required":false},"date_gmt":{"required":false},"karma":{"required":false},"parent":{"required":false},"post":{"required":false},"status":{"required":false},"type":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"comment","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"author":{"description":"The id of the user object, if author was a user.","type":"integer","context":["view","edit","embed"]},"author_avatar_urls":{"description":"Avatar URLs for the object author.","type":"object","context":["view","edit","embed"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}},"author_email":{"description":"Email address for the object author.","type":"string","format":"email","context":["edit"]},"author_ip":{"description":"IP address for the object author.","type":"string","context":["edit"],"readonly":true},"author_name":{"description":"Display name for the object author.","type":"string","context":["view","edit","embed"]},"author_url":{"description":"URL for the object author.","type":"string","format":"uri","context":["view","edit","embed"]},"author_user_agent":{"description":"User agent for the object author.","type":"string","context":["edit"],"readonly":true},"content":{"description":"The content for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published as GMT.","type":"string","format":"date-time","context":["view","edit"]},"karma":{"description":"Karma for the object.","type":"integer","context":["edit"]},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"post":{"description":"The id of the associated post object.","type":"integer","context":["view","edit"]},"status":{"description":"State of the object.","type":"string","context":["view","edit"]},"type":{"description":"Type of Comment for the object.","type":"string","context":["view","edit","embed"]}}}},"\/oembed\/1.0":{"namespace":"oembed\/1.0","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"namespace":{"required":false,"default":"oembed\/1.0"},"context":{"required":false,"default":"view"}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/oembed\/1.0"}},"\/oembed\/1.0\/embed":{"namespace":"oembed\/1.0","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"url":{"required":true},"format":{"required":false,"default":"json"},"maxwidth":{"required":false,"default":600}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/oembed\/1.0\/embed"}},"\/wp\/v2\/posts\/(?P[\\d]+)\/meta":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST"],"args":{"key":{"required":true},"value":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}},"\/wp\/v2\/posts\/(?P[\\d]+)\/meta\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"key":{"required":false},"value":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/meta":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST"],"args":{"key":{"required":true},"value":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/meta\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"key":{"required":false},"value":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}}},"_links":{"help":[{"href":"http:\/\/v2.wp-api.org\/"}]}} diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index 51e23d10..72b18bab 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -6,44 +6,13 @@ var util = require( 'util' ); function logFull( obj ) { console.log( util.inspect( obj, { + colors: true, depth: null }) ); } // All valid routes in API v2 beta 11 -var routes = { - '/': { namespace: '' }, - '/wp/v2': { namespace: 'wp/v2' }, - '/wp/v2/posts': { namespace: 'wp/v2' }, - '/wp/v2/posts/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/posts/(?P[\\d]+)/meta': { namespace: 'wp/v2' }, - '/wp/v2/posts/(?P[\\d]+)/meta/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/posts/(?P[\\d]+)/revisions': { namespace: 'wp/v2' }, - '/wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/pages': { namespace: 'wp/v2' }, - '/wp/v2/pages/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/pages/(?P[\\d]+)/meta': { namespace: 'wp/v2' }, - '/wp/v2/pages/(?P[\\d]+)/meta/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/pages/(?P[\\d]+)/revisions': { namespace: 'wp/v2' }, - '/wp/v2/pages/(?P[\\d]+)/revisions/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/media': { namespace: 'wp/v2' }, - '/wp/v2/media/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/types': { namespace: 'wp/v2' }, - '/wp/v2/types/(?P[\\w-]+)': { namespace: 'wp/v2' }, - '/wp/v2/statuses': { namespace: 'wp/v2' }, - '/wp/v2/statuses/(?P[\\w-]+)': { namespace: 'wp/v2' }, - '/wp/v2/taxonomies': { namespace: 'wp/v2' }, - '/wp/v2/taxonomies/(?P[\\w-]+)': { namespace: 'wp/v2' }, - '/wp/v2/categories': { namespace: 'wp/v2' }, - '/wp/v2/categories/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/tags': { namespace: 'wp/v2' }, - '/wp/v2/tags/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/users': { namespace: 'wp/v2' }, - '/wp/v2/users/(?P[\\d]+)': { namespace: 'wp/v2' }, - '/wp/v2/users/me': { namespace: 'wp/v2' }, - '/wp/v2/comments': { namespace: 'wp/v2' }, - '/wp/v2/comments/(?P[\\d]+)': { namespace: 'wp/v2' } -}; +var routes = require( './endpoint-response.json' ).routes; // Regular Expression to identify a capture group in PCRE formats `(?regex)`, // `(?'name'regex)` or `(?Pregex)` (see regular-expressions.info/refext.html), @@ -65,7 +34,8 @@ var namedGroupRegexp = new RegExp([ ].join( '' ) ); var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route ) { - var nsForRoute = routes[ route ].namespace; + var routeObj = routes[ route ]; + var nsForRoute = routeObj.namespace; // Do not make a namespace group for the API root if ( ! nsForRoute ) { return namespaces; @@ -162,9 +132,16 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route }; // Check to see whether to expect more nodes within this branch of the tree, - // and create a "children" object to hold those nodes if necessary if ( components[ idx + 1 ] ) { + // and create a "children" object to hold those nodes if necessary currentLevel.children = currentLevel.children || {}; + } else { + // At leaf nodes, specify the method capabilities of this endpoint + currentLevel.methods = routeObj.methods; + // And label it with the title of this endpoint's resource, it present + if ( routeObj.schema && routeObj.schema.title ) { + currentLevel.title = routeObj.schema.title; + } } // Return the child node object as the new "level" @@ -186,14 +163,12 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { var nsRoutes = routesByNamespace[ namespace ]; var endpointHandlers = Object.keys( nsRoutes ).reduce(function( handlers, resource ) { var levels = nsRoutes[ resource ]; - console.log( resource ); - logFull( levels ); + // console.log( resource ); + // logFull( levels ); }, {} ) }); -// console.log( util.inspect( routesByNamespace, { -// depth: null -// }) ); +logFull( routesByNamespace ); /* Object.keys( routes ).forEach(function( route ) { From 219342eb0a615df37b3097af36d2fdf844305764 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 12 Feb 2016 12:35:26 -0500 Subject: [PATCH 08/32] Modularize the code a bit and begin iterating on validation/setter derivation --- lib/build-route-tree.js | 130 ++++++++++++++++++++++ lib/named-group-regexp.js | 24 +++++ lib/parse-route-string.js | 219 ++++++++++++++------------------------ 3 files changed, 232 insertions(+), 141 deletions(-) create mode 100644 lib/build-route-tree.js create mode 100644 lib/named-group-regexp.js diff --git a/lib/build-route-tree.js b/lib/build-route-tree.js new file mode 100644 index 00000000..537ee3ba --- /dev/null +++ b/lib/build-route-tree.js @@ -0,0 +1,130 @@ +'use strict'; + +var namedGroupRegexp = require( './named-group-regexp' ); + +function buildRouteTree( routes ) { + return Object.keys( routes ).reduce(function( namespaces, route ) { + var routeObj = routes[ route ]; + var nsForRoute = routeObj.namespace; + // Do not make a namespace group for the API root + // Do not add the namespace root to its own group + if ( ! nsForRoute || '/' + nsForRoute === route ) { + return namespaces; + } + + // Ensure that the namespace object for this namespace exists + if ( ! namespaces[ nsForRoute ] ) { + namespaces[ nsForRoute ] = {}; + } + + // Get a local reference to namespace object + var ns = namespaces[ nsForRoute ]; + + // Strip the namespace from the route string (all routes should have the + // format `/namespace/other/stuff`) @TODO: Validate this assumption + var routeString = route.replace( '/' + nsForRoute + '/', '' ); + + // If no route string, carry on + if ( ! routeString ) { return namespaces; } + + var routeComponents = routeString.split( '/' ); + + // If no components, carry on + if ( ! routeComponents.length ) { return namespaces; } + + // The first element of the route tells us what type of resource this route + // is for, e.g. "posts" or "comments": we build one handler per resource + // type, so we group like resource paths together. + var resource = routeComponents.shift(); + + // @TODO: This code above currently precludes baseless routes, e.g. + // myplugin/v2/(?P\w+) -- should those be supported? + + // Create an array to represent this resource, and ensure it is assigned + // to the namespace object. The array will structure the "levels" (path + // components and subresource types) of this resource's endpoint handler. + var levels = ns[ resource ] || {}; + ns[ resource ] = levels; + + routeComponents.reduce(function( parentLevel, component, idx, components ) { + // Check to see if this component is a dynamic URL segment (i.e. defined by + // a named capture group regular expression). namedGroup will be `null` if + // the regexp does not match, or else an array defining the RegExp match, e.g. + // [ + // 'P[\\d]+)', + // 'id', // Name of the group + // '[\\d]+', // regular expression for this URL segment's contents + // index: 15, + // input: '/wp/v2/posts/(?P[\\d]+)' + // ] + var namedGroup = component.match( namedGroupRegexp ); + // Pull out references to the relevant indices of the match, for utility: + // `null` checking is necessary in case the component did not match the RE, + // hence the `namedGroup &&`. + var groupName = namedGroup && namedGroup[ 1 ]; + var groupPattern = namedGroup && namedGroup[ 2 ]; + + // When branching based on a dynamic capture group we used the group's RE + // pattern as the unique identifier: this is done because the same group + // could be assigned different names in different endpoint handlers, e.g. + // "id" for posts/:id vs "parent_id" for posts/:parent_id/revisions. + var levelKey = namedGroup ? groupPattern : component; + + // Level name, on the other hand, would take its value from the group's name + var levelName = namedGroup ? groupName : component; + + // Check whether we have a preexisting node at this level of the tree, and + // create a new level object if not + var currentLevel = parentLevel[ levelKey ] || { + dynamic: namedGroup ? true : false, + level: idx, + names: [] + }; + + // A level's "name" corresponds to the list of strings which could describe + // an endpoint's component setter functions: "id", "revisions", etc. + if ( currentLevel.names.indexOf( levelName ) < 0 ) { + currentLevel.names.push( levelName ); + } + + // A level's validate method is called to check whether a value being set + // on the request URL is of the proper type for the location in which it + // is specified. If a group pattern was found, the validator checks whether + // the input string exactly matches the group pattern. + var groupPatternRE = groupPattern && new RegExp( '^' + groupPattern + '$' ); + + // Only one validate function is maintained for each node, because each node + // is defined either by a string literal or by a specific regular expression. + currentLevel.validate = function( input ) { + return groupPatternRE ? groupPatternRE.test( input ) : input === component; + }; + + // Check to see whether to expect more nodes within this branch of the tree, + if ( components[ idx + 1 ] ) { + // and create a "children" object to hold those nodes if necessary + currentLevel.children = currentLevel.children || {}; + } else { + // At leaf nodes, specify the method capabilities of this endpoint + currentLevel.methods = routeObj.methods ? routeObj.methods.map(function( str ) { + return str.toLowerCase(); + }) : []; + + // Label node with the title of this endpoint's resource, if available + if ( routeObj.schema && routeObj.schema.title ) { + currentLevel.title = routeObj.schema.title; + } + } + + // Return the child node object as the new "level" + parentLevel[ levelKey ] = currentLevel; + return currentLevel.children; + }, levels ); + + // namespaces[ nsForRoute ] = levels; + // namespaces[ nsForRoute ].routes.push( routeString ); + + return namespaces; + }, {} ); +} + +module.exports = buildRouteTree; diff --git a/lib/named-group-regexp.js b/lib/named-group-regexp.js new file mode 100644 index 00000000..b3de2749 --- /dev/null +++ b/lib/named-group-regexp.js @@ -0,0 +1,24 @@ + +/** + * Regular Expression to identify a capture group in PCRE formats + * `(?regex)`, `(?'name'regex)` or `(?Pregex)` (see + * regular-expressions.info/refext.html); RegExp is built as a string + * to enable more detailed annotation. + * + * @type {RegExp} + */ +module.exports = new RegExp([ + // Capture group start + '\\(\\?', + // Capture group name begins either `P<`, `<` or `'` + '(?:P<|<|\')', + // Everything up to the next `>`` or `'` (depending) will be the capture group name + '([^>\']+)', + // Capture group end + '[>\']', + // Get everything up to the end of the capture group: this is the RegExp used + // when matching URLs to this route, which we can use for validation purposes. + '([^\\)]+)', + // Capture group end + '\\)' +].join( '' ) ); diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index 72b18bab..d0a2722a 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -4,6 +4,7 @@ * @module parseRouteString */ var util = require( 'util' ); +var _ = require( 'lodash' ); function logFull( obj ) { console.log( util.inspect( obj, { colors: true, @@ -14,146 +15,56 @@ function logFull( obj ) { // All valid routes in API v2 beta 11 var routes = require( './endpoint-response.json' ).routes; -// Regular Expression to identify a capture group in PCRE formats `(?regex)`, -// `(?'name'regex)` or `(?Pregex)` (see regular-expressions.info/refext.html), -// built as a string to enable more detailed annotation. -var namedGroupRegexp = new RegExp([ - // Capture group start - '\\(\\?', - // Capture group name begins either `P<`, `<` or `'` - '(?:P<|<|\')', - // Everything up to the next `>`` or `'` (depending) will be the capture group name - '([^>\']+)', - // Capture group end - '[>\']', - // Get everything up to the end of the capture group: this is the RegExp used - // when matching URLs to this route, which we can use for validation purposes. - '([^\\)]+)', - // Capture group end - '\\)' -].join( '' ) ); - -var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route ) { - var routeObj = routes[ route ]; - var nsForRoute = routeObj.namespace; - // Do not make a namespace group for the API root - if ( ! nsForRoute ) { - return namespaces; - } - - // Do not add the namespace root to its own group - if ( '/' + nsForRoute === route ) { - return namespaces; - } - - // Ensure that the namespace object for this namespace exists - if ( ! namespaces[ nsForRoute ] ) { - namespaces[ nsForRoute ] = {}; - } +function forIn( obj, callbackFn ) { + Object.keys( obj ).forEach(function( key, idx ) { + callbackFn( obj[ key ], idx ); + }); +} - // Get a local reference to namespace object - var ns = namespaces[ nsForRoute ]; - - // Strip the namespace from the route string (all routes should have the - // format `/namespace/other/stuff`) @TODO: Validate this assumption - var routeString = route.replace( '/' + nsForRoute + '/', '' ); - - // If no route string, carry on - if ( ! routeString ) { return namespaces; } - - var routeComponents = routeString.split( '/' ); - - // If no components, carry on - if ( ! routeComponents.length ) { return namespaces; } - - // The first element of the route tells us what type of resource this route - // is for, e.g. "posts" or "comments": we build one handler per resource - // type, so we group like resource paths together. - var resource = routeComponents.shift(); - - // @TODO: This code above currently precludes baseless routes, e.g. - // myplugin/v2/(?P\w+) -- should those be supported? - - // Create an array to represent this resource, and ensure it is assigned - // to the namespace object. The array will structure the "levels" (path - // components and subresource types) of this resource's endpoint handler. - var levels = ns[ resource ] || {}; - ns[ resource ] = levels; - - routeComponents.reduce(function( parentLevel, component, idx, components ) { - // Check to see if this component is a dynamic URL segment (i.e. defined by - // a named capture group regular expression). namedGroup will be `null` if - // the regexp does not match, or else an array defining the RegExp match, e.g. - // [ - // 'P[\\d]+)', - // 'id', // Name of the group - // '[\\d]+', // regular expression for this URL segment's contents - // index: 15, - // input: '/wp/v2/posts/(?P[\\d]+)' - // ] - var namedGroup = component.match( namedGroupRegexp ); - // Pull out references to the relevant indices of the match, for utility: - // `null` checking is necessary in case the component did not match the RE, - // hence the `namedGroup &&`. - var groupName = namedGroup && namedGroup[ 1 ]; - var groupPattern = namedGroup && namedGroup[ 2 ]; - - // When branching based on a dynamic capture group we used the group's RE - // pattern as the unique identifier: this is done because the same group - // could be assigned different names in different endpoint handlers, e.g. - // "id" for posts/:id vs "parent_id" for posts/:parent_id/revisions. - var levelKey = namedGroup ? groupPattern : component; - - // Level name, on the other hand, would take its value from the group's name - var levelName = namedGroup ? groupName : component; - - // Check whether we have a preexisting node at this level of the tree, and - // create a new level object if not - var currentLevel = parentLevel[ levelKey ] || { - names: [] - }; - - // A level's "name" corresponds to the list of strings which could describe - // an endpoint's component setter functions: "id", "revisions", etc. - if ( currentLevel.names.indexOf( levelName ) < 0 ) { - currentLevel.names.push( levelName ); - } +var buildRouteTree = require( './build-route-tree' ); +var routesByNamespace = buildRouteTree( routes ); + +function Handler( namespace, resource ) { + this._namespace = namespace || ''; + this._base = resource || ''; + // a path component is a "level"-keyed representation of the contents of + // one section of the requested URL, e.g. `_path: { "0": 17 }` for + // post ID #17 + this._path = {}; + // A "level" is a level-keyed object representing the valid options for + // one level of the resource URL + this._levels = {}; + this._levelValidators = {}; +} - // A level's validate method is called to check whether a value being set - // on the request URL is of the proper type for the location in which it - // is specified. If a group pattern was found, the validator checks whether - // the input string exactly matches the group pattern. - var groupPatternRE = groupPattern && new RegExp( '^' + groupPattern + '$' ); - - // Only one validate function is maintained for each node, because each node - // is defined either by a string literal or by a specific regular expression. - currentLevel.validate = function( input ) { - return groupPatternRE ? groupPatternRE.test( input ) : input === component; - }; - - // Check to see whether to expect more nodes within this branch of the tree, - if ( components[ idx + 1 ] ) { - // and create a "children" object to hold those nodes if necessary - currentLevel.children = currentLevel.children || {}; - } else { - // At leaf nodes, specify the method capabilities of this endpoint - currentLevel.methods = routeObj.methods; - // And label it with the title of this endpoint's resource, it present - if ( routeObj.schema && routeObj.schema.title ) { - currentLevel.title = routeObj.schema.title; - } +Handler.prototype.addLevel = function( level, obj ) { + this._levels[ level ] = this._levels[ level ] || []; + this._levels[ level ].push( obj ); +}; + +Handler.prototype.addLevelValidator = function( level, validator ) { + this._levelValidators[ level ] = this._levelValidators[ level ] || []; + this._levelValidators[ level ].push( validator ); +}; + +/** WIP */ +Handler.prototype.validate = function() { + // Iterate through all _specified_ levels of this endpoint + var specifiedLevels = Object.keys( this._path ) + .map(function( level ) { + return parseInt( level, 10 ); + }) + .sort(function( a, b ) { return a - b; }); + + var maxLevel = Math.max.apply( null, specifiedLevels ); + // Ensure that all necessary levels are specified + for ( var level = 0; level < maxLevel; level++ ) { + if ( ! this._path[ level ] ) { + console.error( 'No value specified for level ' + level ); + console.error( '(expected ' + this._levels[ level ].names.join( '/' ) + ')' ); } - - // Return the child node object as the new "level" - parentLevel[ levelKey ] = currentLevel; - return currentLevel.children; - }, levels ); - - // namespaces[ nsForRoute ] = levels; - // namespaces[ nsForRoute ].routes.push( routeString ); - - return namespaces; -}, {} ); + } +}; // Now that our namespace and levels object has been defined, recurse through // the node tree representing all possible routes within that namespace to @@ -162,13 +73,39 @@ var routesByNamespace = Object.keys( routes ).reduce(function( namespaces, route Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { var nsRoutes = routesByNamespace[ namespace ]; var endpointHandlers = Object.keys( nsRoutes ).reduce(function( handlers, resource ) { + var handler = new Handler( namespace, resource ); var levels = nsRoutes[ resource ]; - // console.log( resource ); - // logFull( levels ); - }, {} ) + + logFull( levels ); + + /** + * Walk the tree + * @param {Object} node A node object + * @param {Object} [node.children] An object of child nodes + * // @return {isLeaf} A boolean indicating whether the processed node is a leaf + */ + function extractSetterFromNode( node ) { + // For each node, add its handler to the relevant "level" representation + handler.addLevel( node.level, _.omit( node, 'children' ) ); + handler.addLevelValidator( node.level, node.validate ); + + // console.log( node ); + if ( node.children ) { + forIn( node.children, extractSetterFromNode ); + } + } + + forIn( levels, extractSetterFromNode ); + + handlers[ resource ] = handler; + return handlers; + }, {} ); + + logFull( endpointHandlers ); }); -logFull( routesByNamespace ); + +// logFull( routesByNamespace ); /* Object.keys( routes ).forEach(function( route ) { From 9a352b438ac5d58f504f44c36b47a6aaa313600f Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 12 Feb 2016 14:14:15 -0500 Subject: [PATCH 09/32] explain problem --- lib/build-route-tree.js | 35 +++++++++++++++++------------------ lib/parse-route-string.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/lib/build-route-tree.js b/lib/build-route-tree.js index 537ee3ba..0f1ef4e8 100644 --- a/lib/build-route-tree.js +++ b/lib/build-route-tree.js @@ -2,36 +2,35 @@ var namedGroupRegexp = require( './named-group-regexp' ); +function ensure( obj, prop, propDefaultValue ) { + if ( ! obj[ prop ] ) { + obj[ prop ] = propDefaultValue; + } +} + function buildRouteTree( routes ) { return Object.keys( routes ).reduce(function( namespaces, route ) { var routeObj = routes[ route ]; var nsForRoute = routeObj.namespace; + + // Strip the namespace from the route string (all routes should have the + // format `/namespace/other/stuff`) @TODO: Validate this assumption + var routeString = route.replace( '/' + nsForRoute + '/', '' ); + var routeComponents = routeString.split( '/' ); + // Do not make a namespace group for the API root // Do not add the namespace root to its own group - if ( ! nsForRoute || '/' + nsForRoute === route ) { + // Do not take any action if routeString is empty + if ( ! nsForRoute || '/' + nsForRoute === route || ! routeString ) { return namespaces; } // Ensure that the namespace object for this namespace exists - if ( ! namespaces[ nsForRoute ] ) { - namespaces[ nsForRoute ] = {}; - } + ensure( namespaces, nsForRoute, {} ); // Get a local reference to namespace object var ns = namespaces[ nsForRoute ]; - // Strip the namespace from the route string (all routes should have the - // format `/namespace/other/stuff`) @TODO: Validate this assumption - var routeString = route.replace( '/' + nsForRoute + '/', '' ); - - // If no route string, carry on - if ( ! routeString ) { return namespaces; } - - var routeComponents = routeString.split( '/' ); - - // If no components, carry on - if ( ! routeComponents.length ) { return namespaces; } - // The first element of the route tells us what type of resource this route // is for, e.g. "posts" or "comments": we build one handler per resource // type, so we group like resource paths together. @@ -43,8 +42,8 @@ function buildRouteTree( routes ) { // Create an array to represent this resource, and ensure it is assigned // to the namespace object. The array will structure the "levels" (path // components and subresource types) of this resource's endpoint handler. - var levels = ns[ resource ] || {}; - ns[ resource ] = levels; + ensure( ns, resource, {} ); + var levels = ns[ resource ]; routeComponents.reduce(function( parentLevel, component, idx, components ) { // Check to see if this component is a dynamic URL segment (i.e. defined by diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index d0a2722a..c7502942 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -21,9 +21,43 @@ function forIn( obj, callbackFn ) { }); } +/* + +"/wp/v2/posts": {}, +"/wp/v2/posts/(?P[\\d]+)": {}, +"/wp/v2/posts/(?P[\\d]+)/revisions": {}, +"/wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+)": {}, +"/wp/v2/pages": {}, +"/wp/v2/pages/(?P[\\d]+)": {}, +"/wp/v2/pages/(?P[\\d]+)/revisions": {}, +"/wp/v2/pages/(?P[\\d]+)/revisions/(?P[\\d]+)": {}, + + */ + var buildRouteTree = require( './build-route-tree' ); var routesByNamespace = buildRouteTree( routes ); +logFull( routesByNamespace ); + +/* + +I want to deduce the following API from this tree (or one like it): + +wp.posts(); /wp/v2/posts +wp.posts().id( 7 ); /wp/v2/posts/7 +wp.posts().id().revisions(); /wp/v2/posts/7/revisions +wp.posts().id().revisions( 8 ); /wp/v2/posts/7/revisions/8 + +^ That last one's the tricky one: I can deduce that this parameter is "id", but +that param will already be taken by the post ID, so sub-collections have to be +set up as `.revisions()` to get the collection, and `.revisions( id )` to get a +specific resource. + +*/ + + +// Flailing around trying to parse all this below + function Handler( namespace, resource ) { this._namespace = namespace || ''; this._base = resource || ''; From d74a1abe9f86d9bb0d0a86ed64d45b7b0af1c50a Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 12 Feb 2016 17:25:19 -0500 Subject: [PATCH 10/32] Initial tweaks to tree structure following code review with gnarf --- lib/build-route-tree.js | 12 ++++++------ lib/parse-route-string.js | 17 +++++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/build-route-tree.js b/lib/build-route-tree.js index 0f1ef4e8..419cf596 100644 --- a/lib/build-route-tree.js +++ b/lib/build-route-tree.js @@ -34,7 +34,7 @@ function buildRouteTree( routes ) { // The first element of the route tells us what type of resource this route // is for, e.g. "posts" or "comments": we build one handler per resource // type, so we group like resource paths together. - var resource = routeComponents.shift(); + var resource = routeComponents[0]; // @TODO: This code above currently precludes baseless routes, e.g. // myplugin/v2/(?P\w+) -- should those be supported? @@ -75,7 +75,7 @@ function buildRouteTree( routes ) { // Check whether we have a preexisting node at this level of the tree, and // create a new level object if not var currentLevel = parentLevel[ levelKey ] || { - dynamic: namedGroup ? true : false, + namedGroup: namedGroup ? true : false, level: idx, names: [] }; @@ -108,10 +108,10 @@ function buildRouteTree( routes ) { return str.toLowerCase(); }) : []; - // Label node with the title of this endpoint's resource, if available - if ( routeObj.schema && routeObj.schema.title ) { - currentLevel.title = routeObj.schema.title; - } + // // Label node with the title of this endpoint's resource, if available + // if ( routeObj.schema && routeObj.schema.title ) { + // currentLevel.title = routeObj.schema.title; + // } } // Return the child node object as the new "level" diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index c7502942..e2c6a780 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -15,9 +15,9 @@ function logFull( obj ) { // All valid routes in API v2 beta 11 var routes = require( './endpoint-response.json' ).routes; -function forIn( obj, callbackFn ) { - Object.keys( obj ).forEach(function( key, idx ) { - callbackFn( obj[ key ], idx ); +function getValues( obj ) { + return Object.keys( obj ).map(function( key ) { + return obj[ key ]; }); } @@ -119,23 +119,28 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { * // @return {isLeaf} A boolean indicating whether the processed node is a leaf */ function extractSetterFromNode( node ) { + var setterFn; + // For each node, add its handler to the relevant "level" representation handler.addLevel( node.level, _.omit( node, 'children' ) ); handler.addLevelValidator( node.level, node.validate ); // console.log( node ); if ( node.children ) { - forIn( node.children, extractSetterFromNode ); + getValues( node.children ).map( extractSetterFromNode ); } } - forIn( levels, extractSetterFromNode ); + getValues( levels ).map( extractSetterFromNode ); handlers[ resource ] = handler; + + logFull( handler ); + return handlers; }, {} ); - logFull( endpointHandlers ); + // logFull( endpointHandlers ); }); From 27cd3d37e1f4783c6f526a3fdebdc73f3796b62c Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 12 Feb 2016 18:40:10 -0500 Subject: [PATCH 11/32] Begin the dynamic generation/construction of setter methods --- lib/parse-route-string.js | 71 ++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index e2c6a780..8647d100 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -16,9 +16,9 @@ function logFull( obj ) { var routes = require( './endpoint-response.json' ).routes; function getValues( obj ) { - return Object.keys( obj ).map(function( key ) { + return typeof obj === 'object' ? Object.keys( obj ).map(function( key ) { return obj[ key ]; - }); + }) : []; } /* @@ -37,7 +37,7 @@ function getValues( obj ) { var buildRouteTree = require( './build-route-tree' ); var routesByNamespace = buildRouteTree( routes ); -logFull( routesByNamespace ); +// logFull( routesByNamespace ); /* @@ -60,17 +60,27 @@ specific resource. function Handler( namespace, resource ) { this._namespace = namespace || ''; - this._base = resource || ''; // a path component is a "level"-keyed representation of the contents of // one section of the requested URL, e.g. `_path: { "0": 17 }` for // post ID #17 - this._path = {}; + this._path = { + '0': resource + }; // A "level" is a level-keyed object representing the valid options for // one level of the resource URL this._levels = {}; this._levelValidators = {}; } +Handler.prototype.setPathPart = function( level, val ) { + if ( this._path[ level ] ) { + throw new Error( 'Cannot overwrite value ' + this._path[ level ] ); + } + this._path[ level ] = val; + + return this; +} + Handler.prototype.addLevel = function( level, obj ) { this._levels[ level ] = this._levels[ level ] || []; this._levels[ level ].push( obj ); @@ -92,12 +102,19 @@ Handler.prototype.validate = function() { var maxLevel = Math.max.apply( null, specifiedLevels ); // Ensure that all necessary levels are specified + var path = []; + var valid = true; for ( var level = 0; level < maxLevel; level++ ) { - if ( ! this._path[ level ] ) { - console.error( 'No value specified for level ' + level ); - console.error( '(expected ' + this._levels[ level ].names.join( '/' ) + ')' ); + if ( this._path[ level ] ) { + path.push( this._path[ level ] ); + } else { + path.push( ' ??? ' ); + valid = false; } } + if ( ! valid ) { + throw new Error( 'Incomplete URL! Missing component: ' + path.join( '/' ) ); + } }; // Now that our namespace and levels object has been defined, recurse through @@ -110,7 +127,7 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { var handler = new Handler( namespace, resource ); var levels = nsRoutes[ resource ]; - logFull( levels ); + // logFull( levels ); /** * Walk the tree @@ -119,12 +136,40 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { * // @return {isLeaf} A boolean indicating whether the processed node is a leaf */ function extractSetterFromNode( node ) { - var setterFn; + var dynamicChildren = getValues( node.children ).filter(function( childNode ) { + return childNode.namedGroup === true; + }); // For each node, add its handler to the relevant "level" representation - handler.addLevel( node.level, _.omit( node, 'children' ) ); + handler.addLevel( node.level, _.pick( node, 'validate', 'methods' ) ); handler.addLevelValidator( node.level, node.validate ); + var setterFn = node.namedGroup ? function( val ) { + handler.setPathPart( node.level, val ); + } : function( val ) { + handler.setPathPart( node.level, 'GET THIS VALUE SOMEHOW' ); + var dynamicChild = dynamicChildren.length === 1 && dynamicChildren[ 0 ]; + if ( dynamicChild ) { + handler.setPathPart( dynamicChild.level, val ); + } + } + + // First level is set implicitly + if ( node.level > 0 ) { + node.names.forEach(function( name ) { + // camel-case the setter name + var name = name + .toLowerCase() + .replace( /_\w/g, function( match ) { + return match.replace( '_', '' ).toUpperCase(); + }); + // Don't overwrite previously-set methods + if ( ! handler[ name ] ) { + handler[ name ] = setterFn; + } + }); + } + // console.log( node ); if ( node.children ) { getValues( node.children ).map( extractSetterFromNode ); @@ -135,12 +180,12 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { handlers[ resource ] = handler; - logFull( handler ); + // logFull( handler ); return handlers; }, {} ); - // logFull( endpointHandlers ); + logFull( endpointHandlers ); }); From e6fc7f37fa07c380ccb3359fd230cde5eeda9870 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 12 Feb 2016 22:00:40 -0500 Subject: [PATCH 12/32] Finish initial pass at defining value setters --- lib/parse-route-string.js | 94 ++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index 8647d100..d5468792 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -12,6 +12,9 @@ function logFull( obj ) { }) ); } +var CollectionRequest = require( './shared/collection-request' ); +var inherit = require( 'util' ).inherits; + // All valid routes in API v2 beta 11 var routes = require( './endpoint-response.json' ).routes; @@ -69,9 +72,11 @@ function Handler( namespace, resource ) { // A "level" is a level-keyed object representing the valid options for // one level of the resource URL this._levels = {}; - this._levelValidators = {}; + this._setters = {}; } +inherit( Handler, CollectionRequest ); + Handler.prototype.setPathPart = function( level, val ) { if ( this._path[ level ] ) { throw new Error( 'Cannot overwrite value ' + this._path[ level ] ); @@ -81,16 +86,11 @@ Handler.prototype.setPathPart = function( level, val ) { return this; } -Handler.prototype.addLevel = function( level, obj ) { +Handler.prototype.addLevelOption = function( level, obj ) { this._levels[ level ] = this._levels[ level ] || []; this._levels[ level ].push( obj ); }; -Handler.prototype.addLevelValidator = function( level, validator ) { - this._levelValidators[ level ] = this._levelValidators[ level ] || []; - this._levelValidators[ level ].push( validator ); -}; - /** WIP */ Handler.prototype.validate = function() { // Iterate through all _specified_ levels of this endpoint @@ -101,6 +101,7 @@ Handler.prototype.validate = function() { .sort(function( a, b ) { return a - b; }); var maxLevel = Math.max.apply( null, specifiedLevels ); + // Ensure that all necessary levels are specified var path = []; var valid = true; @@ -115,6 +116,7 @@ Handler.prototype.validate = function() { if ( ! valid ) { throw new Error( 'Incomplete URL! Missing component: ' + path.join( '/' ) ); } + console.log( path.join( '/' ) ); }; // Now that our namespace and levels object has been defined, recurse through @@ -127,8 +129,6 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { var handler = new Handler( namespace, resource ); var levels = nsRoutes[ resource ]; - // logFull( levels ); - /** * Walk the tree * @param {Object} node A node object @@ -141,17 +141,54 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { }); // For each node, add its handler to the relevant "level" representation - handler.addLevel( node.level, _.pick( node, 'validate', 'methods' ) ); - handler.addLevelValidator( node.level, node.validate ); - - var setterFn = node.namedGroup ? function( val ) { - handler.setPathPart( node.level, val ); - } : function( val ) { - handler.setPathPart( node.level, 'GET THIS VALUE SOMEHOW' ); - var dynamicChild = dynamicChildren.length === 1 && dynamicChildren[ 0 ]; - if ( dynamicChild ) { - handler.setPathPart( dynamicChild.level, val ); - } + handler.addLevelOption( node.level, _.pick( node, 'validate', 'methods' ) ); + + var setterFn; + if ( node.namedGroup ) { + /** + * Set a dymanic (named-group) path part of a query URL. + * + * @chainable + * @param {String|Number} val The path part value to set + * @return {Object} The handler instance (for chaining) + */ + setterFn = function( val ) { + /* jshint validthis:true */ + this.setPathPart( node.level, val ); + return this; + }; + } else { + /** + * Set a non-dymanic (non-named-group) path part of a query URL, and + * set the value of a subresource if an input value is provided and + * exactly one named-group child node exists. + * + * @example + * + * // revisions() is a non-dynamic path part setter: + * wp.posts().id( 4 ).revisions(); // Get posts/4/revisions + * wp.posts().id( 4 ).revisions( 1372 ); // Get posts/4/revisions/1372 + * + * @chainable + * @param {String|Number} [val] The path part value to set (if provided) + * for a subresource within this resource + * @return {Object} The handler instance (for chaining) + */ + setterFn = function( val ) { + /* jshint validthis:true */ + // If the path part is not a namedGroup, it should have exactly one + // entry in the names array: use that as the value for this setter, + // as it will usually correspond to a collection endpoint. + this.setPathPart( node.level, node.names[ 0 ] ); + + // If this node has exactly one dynamic child, this method may act as + // a setter for that child node + var dynamicChild = dynamicChildren.length === 1 && dynamicChildren[ 0 ]; + if ( typeof val !== 'undefined' && dynamicChild ) { + this.setPathPart( dynamicChild.level, val ); + } + return this; + }; } // First level is set implicitly @@ -185,19 +222,12 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { return handlers; }, {} ); - logFull( endpointHandlers ); + if ( endpointHandlers.posts ) { + endpointHandlers.posts.id( 4 ).revisions( 52 ).validate(); + } + + // endpointHandlers.posts }); // logFull( routesByNamespace ); - -/* -Object.keys( routes ).forEach(function( route ) { - // All routes will begin with - var nsForRoute = routes[ route ].ns; - // First of all, strip all initial slashes - route = route.replace( /^\//, '' ); - // Next, remove the namespace, if it is currently prefixed - route = route.replace( ns, '' ); -}) -*/ From ab95da4129590f5d80d4fcb159ab1f6ccd880777 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 12 Feb 2016 22:08:45 -0500 Subject: [PATCH 13/32] Break setter fn generation into its own module --- lib/generate-path-part-setter.js | 58 +++++++++++++++++++++++++++++ lib/parse-route-string.js | 64 ++++---------------------------- 2 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 lib/generate-path-part-setter.js diff --git a/lib/generate-path-part-setter.js b/lib/generate-path-part-setter.js new file mode 100644 index 00000000..a2a2428d --- /dev/null +++ b/lib/generate-path-part-setter.js @@ -0,0 +1,58 @@ +'use strict'; + +var getValues = require( 'lodash' ).values; + +function generatePathPartSetter( node ) { + var dynamicChildren = getValues( node.children ).filter(function( childNode ) { + return childNode.namedGroup === true; + }); + + if ( node.namedGroup ) { + /** + * Set a dymanic (named-group) path part of a query URL. + * + * @chainable + * @param {String|Number} val The path part value to set + * @return {Object} The handler instance (for chaining) + */ + return function( val ) { + /* jshint validthis:true */ + this.setPathPart( node.level, val ); + return this; + }; + } else { + /** + * Set a non-dymanic (non-named-group) path part of a query URL, and + * set the value of a subresource if an input value is provided and + * exactly one named-group child node exists. + * + * @example + * + * // revisions() is a non-dynamic path part setter: + * wp.posts().id( 4 ).revisions(); // Get posts/4/revisions + * wp.posts().id( 4 ).revisions( 1372 ); // Get posts/4/revisions/1372 + * + * @chainable + * @param {String|Number} [val] The path part value to set (if provided) + * for a subresource within this resource + * @return {Object} The handler instance (for chaining) + */ + return function( val ) { + /* jshint validthis:true */ + // If the path part is not a namedGroup, it should have exactly one + // entry in the names array: use that as the value for this setter, + // as it will usually correspond to a collection endpoint. + this.setPathPart( node.level, node.names[ 0 ] ); + + // If this node has exactly one dynamic child, this method may act as + // a setter for that child node + var dynamicChild = dynamicChildren.length === 1 && dynamicChildren[ 0 ]; + if ( typeof val !== 'undefined' && dynamicChild ) { + this.setPathPart( dynamicChild.level, val ); + } + return this; + }; + } +} + +module.exports = generatePathPartSetter; diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index d5468792..16b25f52 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -14,15 +14,12 @@ function logFull( obj ) { var CollectionRequest = require( './shared/collection-request' ); var inherit = require( 'util' ).inherits; +var generatePathPartSetter = require( './generate-path-part-setter' ); // All valid routes in API v2 beta 11 var routes = require( './endpoint-response.json' ).routes; -function getValues( obj ) { - return typeof obj === 'object' ? Object.keys( obj ).map(function( key ) { - return obj[ key ]; - }) : []; -} +var getValues = require( 'lodash' ).values; /* @@ -136,63 +133,16 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { * // @return {isLeaf} A boolean indicating whether the processed node is a leaf */ function extractSetterFromNode( node ) { - var dynamicChildren = getValues( node.children ).filter(function( childNode ) { - return childNode.namedGroup === true; - }); + var setterFn; // For each node, add its handler to the relevant "level" representation handler.addLevelOption( node.level, _.pick( node, 'validate', 'methods' ) ); - var setterFn; - if ( node.namedGroup ) { - /** - * Set a dymanic (named-group) path part of a query URL. - * - * @chainable - * @param {String|Number} val The path part value to set - * @return {Object} The handler instance (for chaining) - */ - setterFn = function( val ) { - /* jshint validthis:true */ - this.setPathPart( node.level, val ); - return this; - }; - } else { - /** - * Set a non-dymanic (non-named-group) path part of a query URL, and - * set the value of a subresource if an input value is provided and - * exactly one named-group child node exists. - * - * @example - * - * // revisions() is a non-dynamic path part setter: - * wp.posts().id( 4 ).revisions(); // Get posts/4/revisions - * wp.posts().id( 4 ).revisions( 1372 ); // Get posts/4/revisions/1372 - * - * @chainable - * @param {String|Number} [val] The path part value to set (if provided) - * for a subresource within this resource - * @return {Object} The handler instance (for chaining) - */ - setterFn = function( val ) { - /* jshint validthis:true */ - // If the path part is not a namedGroup, it should have exactly one - // entry in the names array: use that as the value for this setter, - // as it will usually correspond to a collection endpoint. - this.setPathPart( node.level, node.names[ 0 ] ); - - // If this node has exactly one dynamic child, this method may act as - // a setter for that child node - var dynamicChild = dynamicChildren.length === 1 && dynamicChildren[ 0 ]; - if ( typeof val !== 'undefined' && dynamicChild ) { - this.setPathPart( dynamicChild.level, val ); - } - return this; - }; - } - - // First level is set implicitly + // First level is set implicitly, no dedicated setter needed if ( node.level > 0 ) { + + setterFn = generatePathPartSetter( node ); + node.names.forEach(function( name ) { // camel-case the setter name var name = name From 3ff8bc2d450c0dcdd191332e8d44f505762ffe18 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Sat, 13 Feb 2016 20:19:39 -0500 Subject: [PATCH 14/32] Move to defining proper factory methods for each deduced endpoint handler --- lib/parse-route-string.js | 132 ++++++++++++++++++-------------------- lib/shared/wp-request.js | 68 ++++++++++++++++++++ 2 files changed, 131 insertions(+), 69 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index 16b25f52..d95f8821 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -14,6 +14,7 @@ function logFull( obj ) { var CollectionRequest = require( './shared/collection-request' ); var inherit = require( 'util' ).inherits; +var extend = require( 'node.extend' ); var generatePathPartSetter = require( './generate-path-part-setter' ); // All valid routes in API v2 beta 11 @@ -55,76 +56,33 @@ specific resource. */ - -// Flailing around trying to parse all this below - -function Handler( namespace, resource ) { - this._namespace = namespace || ''; - // a path component is a "level"-keyed representation of the contents of - // one section of the requested URL, e.g. `_path: { "0": 17 }` for - // post ID #17 - this._path = { - '0': resource - }; - // A "level" is a level-keyed object representing the valid options for - // one level of the resource URL - this._levels = {}; - this._setters = {}; -} - -inherit( Handler, CollectionRequest ); - -Handler.prototype.setPathPart = function( level, val ) { - if ( this._path[ level ] ) { - throw new Error( 'Cannot overwrite value ' + this._path[ level ] ); - } - this._path[ level ] = val; - - return this; -} - -Handler.prototype.addLevelOption = function( level, obj ) { - this._levels[ level ] = this._levels[ level ] || []; - this._levels[ level ].push( obj ); -}; - -/** WIP */ -Handler.prototype.validate = function() { - // Iterate through all _specified_ levels of this endpoint - var specifiedLevels = Object.keys( this._path ) - .map(function( level ) { - return parseInt( level, 10 ); - }) - .sort(function( a, b ) { return a - b; }); - - var maxLevel = Math.max.apply( null, specifiedLevels ); - - // Ensure that all necessary levels are specified - var path = []; - var valid = true; - for ( var level = 0; level < maxLevel; level++ ) { - if ( this._path[ level ] ) { - path.push( this._path[ level ] ); - } else { - path.push( ' ??? ' ); - valid = false; - } - } - if ( ! valid ) { - throw new Error( 'Incomplete URL! Missing component: ' + path.join( '/' ) ); - } - console.log( path.join( '/' ) ); -}; - // Now that our namespace and levels object has been defined, recurse through // the node tree representing all possible routes within that namespace to // define the path value setters and corresponding validators for all possible // variants of each resource's API endpoints Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { var nsRoutes = routesByNamespace[ namespace ]; + + function addLevelOption( levelsObj, level, obj ) { + levelsObj[ level ] = levelsObj[ level ] || []; + levelsObj[ level ].push( obj ); + } + var endpointHandlers = Object.keys( nsRoutes ).reduce(function( handlers, resource ) { - var handler = new Handler( namespace, resource ); - var levels = nsRoutes[ resource ]; + + var handler = { + _path: { + '0': resource + }, + + // A "level" is a level-keyed object representing the valid options for + // one level of the resource URL + _levels: {}, + + // Objects that hold methods and properties which will be copied to + // instances of this endpoint's handler + _setters: {} + }; /** * Walk the tree @@ -136,7 +94,7 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { var setterFn; // For each node, add its handler to the relevant "level" representation - handler.addLevelOption( node.level, _.pick( node, 'validate', 'methods' ) ); + addLevelOption( handler._levels, node.level, _.pick( node, 'validate', 'methods' ) ); // First level is set implicitly, no dedicated setter needed if ( node.level > 0 ) { @@ -150,9 +108,10 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { .replace( /_\w/g, function( match ) { return match.replace( '_', '' ).toUpperCase(); }); + // Don't overwrite previously-set methods - if ( ! handler[ name ] ) { - handler[ name ] = setterFn; + if ( ! handler._setters[ name ] ) { + handler._setters[ name ] = setterFn; } }); } @@ -163,9 +122,34 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { } } - getValues( levels ).map( extractSetterFromNode ); + // Walk the tree + getValues( nsRoutes[ resource ] ).map( extractSetterFromNode ); + + // Create the constructor function for this endpoint + function EndpointRequest( options ) { + this._options = options || {}; + + this._levels = handler._levels; + this._path = {}; + + // Configure handler for this endpoint + this + .setPathPart( 0, resource ) + .namespace( namespace ); + } + inherit( EndpointRequest, CollectionRequest ); + + Object.keys( handler._setters ).forEach(function( setterFnName ) { + EndpointRequest.prototype[ setterFnName ] = handler._setters[ setterFnName ]; + }); - handlers[ resource ] = handler; + // "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(); + }; // logFull( handler ); @@ -173,7 +157,17 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { }, {} ); if ( endpointHandlers.posts ) { - endpointHandlers.posts.id( 4 ).revisions( 52 ).validate(); + console.log( + endpointHandlers.posts({ + endpoint: 'http://kadamwhite.com/wp-json' + }).id( 4 ).revisions( 52 ).validatePath()._renderPath() + ); + try { + endpointHandlers.posts().revisions( 52 ).validatePath()._renderPath(); + } catch( e ) { console.error( e ); } + console.log( + endpointHandlers.posts().id( 4 ).meta().filter( 'some', 'prop' )._renderPath() + ); } // endpointHandlers.posts diff --git a/lib/shared/wp-request.js b/lib/shared/wp-request.js index 8acdfead..25518ee6 100644 --- a/lib/shared/wp-request.js +++ b/lib/shared/wp-request.js @@ -380,6 +380,74 @@ WPRequest.prototype._renderQuery = function() { return ( queryString === '' ) ? '' : '?' + queryString; }; +/** + * Set a component of the resource URL itself (as opposed to a query parameter) + * + * If a path component has already been set at this level, throw an error: + * requests are meant to be transient, so any re-writing of a previously-set + * path part value is likely to be a mistake. + * + * @method setPathPart + * @chainable + * @param {Number|String} level A "level" of the path to set, e.g. "1" or "2" + * @param {Number|String} val The value to set at that path part level + * @return {WPRequest} The WPRequest instance (for chaining) + */ +WPRequest.prototype.setPathPart = function( level, val ) { + if ( this._path[ level ] ) { + throw new Error( 'Cannot overwrite value ' + this._path[ level ] ); + } + this._path[ level ] = val; + + return this; +}; + +/** + * Validate whether the specified path parts are valid for this endpoint + * + * "Path parts" are non-query-string URL segments, like "some" "path" in the URL + * `mydomain.com/some/path?and=a&query=string&too`. Because a well-formed path + * is necessary to execute a successful API request, we throw an error if the + * user has omitted a value (such as `/some/[missing component]/url`) or has + * provided a path part value that does not match the regular expression the + * API uses to goven that segment. + * + * @method validatePath + * @chainable + * @returns {WPRequest} The WPRequest instance (for chaining), if no errors were found + */ +WPRequest.prototype.validatePath = function() { + // Iterate through all _specified_ levels of this endpoint + var specifiedLevels = Object.keys( this._path ) + .map(function( level ) { + return parseInt( level, 10 ); + }) + .filter(function( pathPartKey ) { + return ! isNaN( pathPartKey ); + }); + + var maxLevel = Math.max.apply( null, specifiedLevels ); + + // Ensure that all necessary levels are specified + var path = []; + var valid = true; + + for ( var level = 0; level < maxLevel; level++ ) { + if ( this._path[ level ] ) { + path.push( this._path[ level ] ); + } else { + path.push( ' ??? ' ); + valid = false; + } + } + + if ( ! valid ) { + throw new Error( 'Incomplete URL! Missing component: ' + path.join( '/' ) ); + } + + return this; +}; + /** * Set a parameter to render into the final query URI. * From 8d6ed9a011cbb1c6b64b2fbc16a1956d600d7274 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Sat, 13 Feb 2016 20:43:09 -0500 Subject: [PATCH 15/32] Break everything, by beginning to fold the new into the old --- lib/parse-route-string.js | 78 ++++++++++++++++++--------------------- lib/shared/wp-request.js | 62 +++++++++++-------------------- wp.js | 22 ++++++----- 3 files changed, 70 insertions(+), 92 deletions(-) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index d95f8821..0112d67d 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -5,15 +5,16 @@ */ var util = require( 'util' ); var _ = require( 'lodash' ); -function logFull( obj ) { - console.log( util.inspect( obj, { - colors: true, - depth: null - }) ); -} + +// function logFull( obj ) { +// console.log( util.inspect( obj, { +// colors: true, +// depth: null +// }) ); +// } var CollectionRequest = require( './shared/collection-request' ); -var inherit = require( 'util' ).inherits; +var inherit = util.inherits; var extend = require( 'node.extend' ); var generatePathPartSetter = require( './generate-path-part-setter' ); @@ -38,8 +39,6 @@ var getValues = require( 'lodash' ).values; var buildRouteTree = require( './build-route-tree' ); var routesByNamespace = buildRouteTree( routes ); -// logFull( routesByNamespace ); - /* I want to deduce the following API from this tree (or one like it): @@ -60,15 +59,14 @@ specific resource. // the node tree representing all possible routes within that namespace to // define the path value setters and corresponding validators for all possible // variants of each resource's API endpoints -Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { - var nsRoutes = routesByNamespace[ namespace ]; - function addLevelOption( levelsObj, level, obj ) { - levelsObj[ level ] = levelsObj[ level ] || []; - levelsObj[ level ].push( obj ); - } +function addLevelOption( levelsObj, level, obj ) { + levelsObj[ level ] = levelsObj[ level ] || []; + levelsObj[ level ].push( obj ); +} - var endpointHandlers = Object.keys( nsRoutes ).reduce(function( handlers, resource ) { +function generateEndpointFactories( namespace, routesArr ) { + var endpointFactories = Object.keys( routesArr ).reduce(function( handlers, resource ) { var handler = { _path: { @@ -103,15 +101,15 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { node.names.forEach(function( name ) { // camel-case the setter name - var name = name + var setterFnName = name .toLowerCase() .replace( /_\w/g, function( match ) { return match.replace( '_', '' ).toUpperCase(); }); // Don't overwrite previously-set methods - if ( ! handler._setters[ name ] ) { - handler._setters[ name ] = setterFn; + if ( ! handler._setters[ setterFnName ] ) { + handler._setters[ setterFnName ] = setterFn; } }); } @@ -123,7 +121,7 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { } // Walk the tree - getValues( nsRoutes[ resource ] ).map( extractSetterFromNode ); + getValues( routesArr[ resource ] ).map( extractSetterFromNode ); // Create the constructor function for this endpoint function EndpointRequest( options ) { @@ -148,30 +146,26 @@ Object.keys( routesByNamespace ).forEach(function( namespace, idx ) { handlers[ resource ] = function( options ) { options = options || {}; options = extend( options, this._options ); - return new EndpointRequest(); + return new EndpointRequest( options ); }; - // logFull( handler ); - return handlers; }, {} ); - if ( endpointHandlers.posts ) { - console.log( - endpointHandlers.posts({ - endpoint: 'http://kadamwhite.com/wp-json' - }).id( 4 ).revisions( 52 ).validatePath()._renderPath() - ); - try { - endpointHandlers.posts().revisions( 52 ).validatePath()._renderPath(); - } catch( e ) { console.error( e ); } - console.log( - endpointHandlers.posts().id( 4 ).meta().filter( 'some', 'prop' )._renderPath() - ); - } - - // endpointHandlers.posts -}); - - -// logFull( routesByNamespace ); + return endpointFactories; +} + +var WP = generateEndpointFactories( 'wp/v2', routesByNamespace[ 'wp/v2' ] ); +// console.log( +// WP.posts({ +// endpoint: 'http://kadamwhite.com/wp-json' +// }).id( 4 ).revisions( 52 ).validatePath()._renderURI() +// ); +// try { +// WP.posts().revisions( 52 ).validatePath()._renderURI(); +// } catch ( e ) { console.error( e ); } +// console.log( +// WP.posts().id( 4 ).meta().filter( 'some', 'prop' )._renderURI() +// ); + +module.exports = WP; diff --git a/lib/shared/wp-request.js b/lib/shared/wp-request.js index 25518ee6..fb833510 100644 --- a/lib/shared/wp-request.js +++ b/lib/shared/wp-request.js @@ -8,7 +8,6 @@ /*jshint -W079 */// Suppress warning about redefiniton of `Promise` var Promise = require( 'bluebird' ); var agent = require( 'superagent' ); -var Route = require( 'route-parser' ); var parseLinkHeader = require( 'li' ).parse; var url = require( 'url' ); var qs = require( 'qs' ); @@ -158,37 +157,6 @@ function returnHeaders( result ) { return result.headers; } -/** - * Check path parameter values against validation regular expressions - * - * @param {Object} pathValues A hash of path placeholder keys and their corresponding values - * @param {Object} validators A hash of placeholder keys to validation regexes - * @return {Object} Returns pathValues if all validation passes (else will throw) - */ -function validatePath( pathValues, validators ) { - if ( ! validators ) { - return pathValues; - } - for ( var param in pathValues ) { - if ( ! pathValues.hasOwnProperty( param ) ) { - continue; - } - - // No validator, no problem - if ( ! validators[ param ] ) { - continue; - } - - // Convert parameter to a string value and check it against the regex - if ( ! ( pathValues[ param ] + '' ).match( validators[ param ] ) ) { - throw new Error( param + ' does not match ' + validators[ param ] ); - } - } - - // If validation passed, return the pathValues object - return pathValues; -} - /** * Process arrays of taxonomy terms into query parameters. * All terms listed in the arrays will be required (AND behavior). @@ -580,16 +548,30 @@ WPRequest.prototype._checkMethodSupport = function( method ) { * @return {String} The rendered path */ WPRequest.prototype._renderPath = function() { + // Call validatePath: if the provided path components are not well-formed, + // an error will be thrown + this.validatePath(); + + var pathParts = this._path; + var orderedPathParts = Object.keys( pathParts ) + .sort(function( a, b ) { + var intA = parseInt( a, 10 ); + var intB = parseInt( b, 10 ); + if ( isNaN( intA ) && isNaN( intB ) ) { + return intA - intB; + } + }) + .map(function( pathPartKey ) { + return pathParts[ pathPartKey ]; + }); + // Combine all parts of the path together, filtered to omit any components // that are unspecified or empty strings, to create the full path template - var template = [ - this._namespace, - this._template - ].filter( identity ).join( '/' ); - var path = new Route( template ); - var pathValues = validatePath( this._path, this._pathValidators ); - - return path.reverse( pathValues ) || ''; + var path = [ + this._namespace + ].concat( orderedPathParts ).filter( identity ).join( '/' ); + + return path; }; /** diff --git a/wp.js b/wp.js index 6558f229..6338f4d0 100644 --- a/wp.js +++ b/wp.js @@ -17,6 +17,12 @@ */ var extend = require( 'node.extend' ); +var generateEndpointFactories = require( './lib/parse-route-string' ); + +var endpointFactories = generateEndpointFactories; + +console.log( endpointFactories ); + var defaults = { username: '', password: '' @@ -103,11 +109,7 @@ WP.prototype.comments = function( options ) { * @param {Object} [options] An options hash for a new MediaRequest * @return {MediaRequest} A MediaRequest instance */ -WP.prototype.media = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new MediaRequest( options ); -}; +WP.prototype.media = endpointFactories.media; /** * Start a request against the `/pages` endpoint @@ -119,7 +121,7 @@ WP.prototype.media = function( options ) { WP.prototype.pages = function( options ) { options = options || {}; options = extend( options, this._options ); - return new PagesRequest( options ); + return new PagesRequest( options ).setPathPart( 0, 'pages' ); }; /** @@ -132,7 +134,7 @@ WP.prototype.pages = function( options ) { WP.prototype.posts = function( options ) { options = options || {}; options = extend( options, this._options ); - return new PostsRequest( options ); + return new PostsRequest( options ).setPathPart( 0, 'posts' ); }; /** @@ -226,7 +228,7 @@ WP.prototype.tags = function() { WP.prototype.types = function( options ) { options = options || {}; options = extend( options, this._options ); - return new TypesRequest( options ); + return new TypesRequest( options ).setPathPart( 0, 'types' ); }; /** @@ -239,7 +241,7 @@ WP.prototype.types = function( options ) { WP.prototype.users = function( options ) { options = options || {}; options = extend( options, this._options ); - return new UsersRequest( options ); + return new UsersRequest( options ).setPathPart( 0, 'users' ); }; /** @@ -280,7 +282,7 @@ WP.prototype.root = function( relativePath, collection ) { var request = collection ? new CollectionRequest( options ) : new WPRequest( options ); // Set the path template to the string passed in - request._template = relativePath; + request._path = { '0': relativePath }; return request; }; From a4c51b4e3199113ab620556a56a4334d08959b63 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Sat, 4 Jun 2016 14:26:00 -0400 Subject: [PATCH 16/32] Small cleanup while getting back in to this codebase --- lib/generate-path-part-setter.js | 16 ++++++++++++++++ lib/parse-route-string.js | 11 +++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/generate-path-part-setter.js b/lib/generate-path-part-setter.js index a2a2428d..f6d0da35 100644 --- a/lib/generate-path-part-setter.js +++ b/lib/generate-path-part-setter.js @@ -2,6 +2,17 @@ var getValues = require( 'lodash' ).values; +/** + * Return a function to set part of the request URL path. + * + * Path part setter methods may be either dynamic (*i.e.* may represent a + * "named group") or non-dynamic (representing a static part of the URL, which + * is usually a collection endpoint of some sort). Which type of function is + * returned depends on whether a given route has one or many sub-resources. + * + * @param {[type]} node [description] + * @returns {[type]} [description] + */ function generatePathPartSetter( node ) { var dynamicChildren = getValues( node.children ).filter(function( childNode ) { return childNode.namedGroup === true; @@ -11,6 +22,11 @@ function generatePathPartSetter( node ) { /** * Set a dymanic (named-group) path part of a query URL. * + * @example + * + * // id() is a dynamic path part setter: + * wp.posts().id( 7 ); // Get posts/7 + * * @chainable * @param {String|Number} val The path part value to set * @return {Object} The handler instance (for chaining) diff --git a/lib/parse-route-string.js b/lib/parse-route-string.js index 0112d67d..cdf3b81d 100644 --- a/lib/parse-route-string.js +++ b/lib/parse-route-string.js @@ -43,10 +43,10 @@ var routesByNamespace = buildRouteTree( routes ); I want to deduce the following API from this tree (or one like it): -wp.posts(); /wp/v2/posts -wp.posts().id( 7 ); /wp/v2/posts/7 -wp.posts().id().revisions(); /wp/v2/posts/7/revisions -wp.posts().id().revisions( 8 ); /wp/v2/posts/7/revisions/8 +wp.posts(); /wp/v2/posts +wp.posts().id( 7 ); /wp/v2/posts/7 +wp.posts().id( 7 ).revisions(); /wp/v2/posts/7/revisions +wp.posts().id( 7 ).revisions( 8 ); /wp/v2/posts/7/revisions/8 ^ That last one's the tricky one: I can deduce that this parameter is "id", but that param will already be taken by the post ID, so sub-collections have to be @@ -66,6 +66,7 @@ function addLevelOption( levelsObj, level, obj ) { } function generateEndpointFactories( namespace, routesArr ) { + console.log( routesArr ); var endpointFactories = Object.keys( routesArr ).reduce(function( handlers, resource ) { var handler = { @@ -116,6 +117,7 @@ function generateEndpointFactories( namespace, routesArr ) { // console.log( node ); if ( node.children ) { + // Recurse down to this node's children getValues( node.children ).map( extractSetterFromNode ); } } @@ -137,6 +139,7 @@ function generateEndpointFactories( namespace, routesArr ) { } inherit( EndpointRequest, CollectionRequest ); + console.log( handler ); Object.keys( handler._setters ).forEach(function( setterFnName ) { EndpointRequest.prototype[ setterFnName ] = handler._setters[ setterFnName ]; }); From 5b8525497eb63862ceb4f96cd8ec2bdec6ac17f5 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Sat, 4 Jun 2016 22:04:07 -0400 Subject: [PATCH 17/32] Rename lib/lib to lib/util & rearrange files --- lib/{ => data}/endpoint-response.json | 0 lib/mixins/filters.js | 2 +- lib/shared/wp-request.js | 2 +- lib/{lib => util}/alphanumeric-sort.js | 0 lib/{ => util}/build-route-tree.js | 0 lib/{ => util}/generate-path-part-setter.js | 0 lib/{ => util}/named-group-regexp.js | 0 lib/{ => util}/parse-route-string.js | 4 ++-- wp.js | 2 +- 9 files changed, 5 insertions(+), 5 deletions(-) rename lib/{ => data}/endpoint-response.json (100%) rename lib/{lib => util}/alphanumeric-sort.js (100%) rename lib/{ => util}/build-route-tree.js (100%) rename lib/{ => util}/generate-path-part-setter.js (100%) rename lib/{ => util}/named-group-regexp.js (100%) rename lib/{ => util}/parse-route-string.js (97%) diff --git a/lib/endpoint-response.json b/lib/data/endpoint-response.json similarity index 100% rename from lib/endpoint-response.json rename to lib/data/endpoint-response.json diff --git a/lib/mixins/filters.js b/lib/mixins/filters.js index 0e785da8..318acc6e 100644 --- a/lib/mixins/filters.js +++ b/lib/mixins/filters.js @@ -9,7 +9,7 @@ */ var _ = require( 'lodash' ); var extend = require( 'node.extend' ); -var alphaNumericSort = require( '../lib/alphanumeric-sort' ); +var alphaNumericSort = require( '../util/alphanumeric-sort' ); var filterMixins = {}; diff --git a/lib/shared/wp-request.js b/lib/shared/wp-request.js index fb833510..06614482 100644 --- a/lib/shared/wp-request.js +++ b/lib/shared/wp-request.js @@ -15,7 +15,7 @@ var _ = require( 'lodash' ); var extend = require( 'node.extend' ); // TODO: reorganize library so that this has a better home -var alphaNumericSort = require( '../lib/alphanumeric-sort' ); +var alphaNumericSort = require( '../util/alphanumeric-sort' ); /** * WPRequest is the base API request object constructor diff --git a/lib/lib/alphanumeric-sort.js b/lib/util/alphanumeric-sort.js similarity index 100% rename from lib/lib/alphanumeric-sort.js rename to lib/util/alphanumeric-sort.js diff --git a/lib/build-route-tree.js b/lib/util/build-route-tree.js similarity index 100% rename from lib/build-route-tree.js rename to lib/util/build-route-tree.js diff --git a/lib/generate-path-part-setter.js b/lib/util/generate-path-part-setter.js similarity index 100% rename from lib/generate-path-part-setter.js rename to lib/util/generate-path-part-setter.js diff --git a/lib/named-group-regexp.js b/lib/util/named-group-regexp.js similarity index 100% rename from lib/named-group-regexp.js rename to lib/util/named-group-regexp.js diff --git a/lib/parse-route-string.js b/lib/util/parse-route-string.js similarity index 97% rename from lib/parse-route-string.js rename to lib/util/parse-route-string.js index cdf3b81d..ba897cd8 100644 --- a/lib/parse-route-string.js +++ b/lib/util/parse-route-string.js @@ -13,13 +13,13 @@ var _ = require( 'lodash' ); // }) ); // } -var CollectionRequest = require( './shared/collection-request' ); +var CollectionRequest = require( '../shared/collection-request' ); var inherit = util.inherits; var extend = require( 'node.extend' ); var generatePathPartSetter = require( './generate-path-part-setter' ); // All valid routes in API v2 beta 11 -var routes = require( './endpoint-response.json' ).routes; +var routes = require( '../data/endpoint-response.json' ).routes; var getValues = require( 'lodash' ).values; diff --git a/wp.js b/wp.js index 6338f4d0..52362d74 100644 --- a/wp.js +++ b/wp.js @@ -17,7 +17,7 @@ */ var extend = require( 'node.extend' ); -var generateEndpointFactories = require( './lib/parse-route-string' ); +var generateEndpointFactories = require( './lib/util/parse-route-string' ); var endpointFactories = generateEndpointFactories; From 204bdf5a1a6a14790f0436ace719f1e9737ca95d Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Sun, 5 Jun 2016 17:39:19 -0500 Subject: [PATCH 18/32] Fragment parse-route-string into multiple modules --- lib/data/endpoint-response.json | 2 +- lib/util/log-obj.js | 10 ++ lib/util/make-endpoint-request.js | 30 ++++++ lib/util/parse-route-string.js | 166 +++--------------------------- lib/util/resource-handler-spec.js | 106 +++++++++++++++++++ wp.js | 13 ++- 6 files changed, 174 insertions(+), 153 deletions(-) create mode 100644 lib/util/log-obj.js create mode 100644 lib/util/make-endpoint-request.js create mode 100644 lib/util/resource-handler-spec.js diff --git a/lib/data/endpoint-response.json b/lib/data/endpoint-response.json index a638397b..d7f28fb1 100644 --- a/lib/data/endpoint-response.json +++ b/lib/data/endpoint-response.json @@ -1 +1 @@ -{"name":"WP-API Testbed","description":"Just another WordPress site","url":"http:\/\/wpapi.loc\/wp","namespaces":["wp\/v2","oembed\/1.0"],"authentication":[],"routes":{"\/":{"namespace":"","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view"}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/"}},"\/wp\/v2":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"namespace":{"required":false,"default":"wp\/v2"},"context":{"required":false,"default":"view"}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2"}},"\/wp\/v2\/posts":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug"],"description":"Sort collection by object attribute."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"publish","description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."}}},{"methods":["POST"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"format":{"required":false,"enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"]},"sticky":{"required":false},"categories":{"required":false},"tags":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"format":{"description":"The format for the object.","type":"string","enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"],"context":["view","edit"]},"sticky":{"description":"Whether or not the object should be treated as sticky.","type":"boolean","context":["view","edit"]},"categories":{"description":"The terms assigned to the object in the category taxonomy.","type":"array","context":["view","edit"]},"tags":{"description":"The terms assigned to the object in the post_tag taxonomy.","type":"array","context":["view","edit"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/posts"}},"\/wp\/v2\/posts\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"format":{"required":false,"enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"]},"sticky":{"required":false},"categories":{"required":false},"tags":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"format":{"description":"The format for the object.","type":"string","enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"],"context":["view","edit"]},"sticky":{"description":"Whether or not the object should be treated as sticky.","type":"boolean","context":["view","edit"]},"categories":{"description":"The terms assigned to the object in the category taxonomy.","type":"array","context":["view","edit"]},"tags":{"description":"The terms assigned to the object in the post_tag taxonomy.","type":"array","context":["view","edit"]}}}},"\/wp\/v2\/posts\/(?P[\\d]+)\/revisions":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/posts\/(?P[\\d]+)\/revisions\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["DELETE"],"args":[]}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"post-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/pages":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"menu_order":{"required":false,"description":"Limit result set to resources with a specific menu_order value."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug","menu_order"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to those of particular parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Limit result set to all items except those of a particular parent id."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"publish","description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."}}},{"methods":["POST"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"parent":{"required":false},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"menu_order":{"required":false},"template":{"required":false,"enum":[]}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit"]},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"menu_order":{"description":"The order of the object in relation to other object of its type.","type":"integer","context":["view","edit"]},"template":{"description":"The theme file to use to display the object.","type":"string","enum":[],"context":["view","edit"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/pages"}},"\/wp\/v2\/pages\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"parent":{"required":false},"title":{"required":false},"content":{"required":false},"author":{"required":false},"excerpt":{"required":false},"featured_media":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"menu_order":{"required":false},"template":{"required":false,"enum":[]}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit"]},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"content":{"description":"The content for the object.","type":"object","context":["view","edit"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"excerpt":{"description":"The excerpt for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML excerpt for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"featured_media":{"description":"The id of the featured media for the object.","type":"integer","context":["view","edit"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"menu_order":{"description":"The order of the object in relation to other object of its type.","type":"integer","context":["view","edit"]},"template":{"description":"The theme file to use to display the object.","type":"string","enum":[],"context":["view","edit"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/revisions":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/revisions\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["DELETE"],"args":[]}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"page-revision","type":"object","properties":{"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["view","edit"]},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"]},"modified":{"description":"The date the object was last modified.","type":"string","format":"date-time","context":["view","edit"]},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"title":{"description":"Title for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]},"content":{"description":"Content for the object, as it exists in the database.","type":"string","context":["view","edit"]},"excerpt":{"description":"Excerpt for the object, as it exists in the database.","type":"string","context":["view","edit","embed"]}}}},"\/wp\/v2\/media":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to those of particular parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Limit result set to all items except those of a particular parent id."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"inherit","enum":["inherit","private","trash"],"description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."},"media_type":{"required":false,"enum":["image","video","text","application","audio"],"description":"Limit result set to attachments of a particular media type."},"mime_type":{"required":false,"description":"Limit result set to attachments of a particular mime type."}}},{"methods":["POST"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"author":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"alt_text":{"required":false},"caption":{"required":false},"description":{"required":false},"post":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"attachment","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"alt_text":{"description":"Alternative text to display when resource is not displayed.","type":"string","context":["view","edit","embed"]},"caption":{"description":"The caption for the resource.","type":"string","context":["view","edit"]},"description":{"description":"The description for the resource.","type":"string","context":["view","edit"]},"media_type":{"description":"Type of resource.","type":"string","enum":["image","file"],"context":["view","edit","embed"],"readonly":true},"mime_type":{"description":"Mime type of resource.","type":"string","context":["view","edit","embed"],"readonly":true},"media_details":{"description":"Details about the resource file, specific to its type.","type":"object","context":["view","edit","embed"],"readonly":true},"post":{"description":"The id for the associated post of the resource.","type":"integer","context":["view","edit"]},"source_url":{"description":"URL to the original resource file.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/media"}},"\/wp\/v2\/media\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false},"date_gmt":{"required":false},"password":{"required":false},"slug":{"required":false},"status":{"required":false,"enum":["publish","future","draft","pending","private"]},"title":{"required":false},"author":{"required":false},"comment_status":{"required":false,"enum":["open","closed"]},"ping_status":{"required":false,"enum":["open","closed"]},"alt_text":{"required":false},"caption":{"required":false},"description":{"required":false},"post":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"attachment","type":"object","properties":{"date":{"description":"The date the object was published, in the site's timezone.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published, as GMT.","type":"string","format":"date-time","context":["view","edit"]},"guid":{"description":"The globally unique identifier for the object.","type":"object","context":["view","edit"],"readonly":true,"properties":{"raw":{"description":"GUID for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"GUID for the object, transformed for display.","type":"string","context":["view","edit"]}}},"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"modified":{"description":"The date the object was last modified, in the site's timezone.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"modified_gmt":{"description":"The date the object was last modified, as GMT.","type":"string","format":"date-time","context":["view","edit"],"readonly":true},"password":{"description":"A password to protect access to the post.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the object unique to its type.","type":"string","context":["view","edit","embed"]},"status":{"description":"A named status for the object.","type":"string","enum":["publish","future","draft","pending","private"],"context":["edit"]},"type":{"description":"Type of Post for the object.","type":"string","context":["view","edit","embed"],"readonly":true},"title":{"description":"The title for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Title for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML title for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"author":{"description":"The id for the author of the object.","type":"integer","context":["view","edit","embed"]},"comment_status":{"description":"Whether or not comments are open on the object.","type":"string","enum":["open","closed"],"context":["view","edit"]},"ping_status":{"description":"Whether or not the object can be pinged.","type":"string","enum":["open","closed"],"context":["view","edit"]},"alt_text":{"description":"Alternative text to display when resource is not displayed.","type":"string","context":["view","edit","embed"]},"caption":{"description":"The caption for the resource.","type":"string","context":["view","edit"]},"description":{"description":"The description for the resource.","type":"string","context":["view","edit"]},"media_type":{"description":"Type of resource.","type":"string","enum":["image","file"],"context":["view","edit","embed"],"readonly":true},"mime_type":{"description":"Mime type of resource.","type":"string","context":["view","edit","embed"],"readonly":true},"media_details":{"description":"Details about the resource file, specific to its type.","type":"object","context":["view","edit","embed"],"readonly":true},"post":{"description":"The id for the associated post of the resource.","type":"integer","context":["view","edit"]},"source_url":{"description":"URL to the original resource file.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true}}}},"\/wp\/v2\/types":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"type","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/types"}},"\/wp\/v2\/types\/(?P[\\w-]+)":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"type","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true}}}},"\/wp\/v2\/statuses":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"status","type":"object","properties":{"name":{"description":"The title for the resource.","type":"string","context":["embed","view","edit"],"readonly":true},"private":{"description":"Whether posts with this resource should be private.","type":"boolean","context":["edit"],"readonly":true},"protected":{"description":"Whether posts with this resource should be protected.","type":"boolean","context":["edit"],"readonly":true},"public":{"description":"Whether posts of this resource should be shown in the front end of the site.","type":"boolean","context":["view","edit"],"readonly":true},"queryable":{"description":"Whether posts with this resource should be publicly-queryable.","type":"boolean","context":["view","edit"],"readonly":true},"show_in_list":{"description":"Whether to include posts in the edit listing for their post type.","type":"boolean","context":["edit"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/statuses"}},"\/wp\/v2\/statuses\/(?P[\\w-]+)":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"status","type":"object","properties":{"name":{"description":"The title for the resource.","type":"string","context":["embed","view","edit"],"readonly":true},"private":{"description":"Whether posts with this resource should be private.","type":"boolean","context":["edit"],"readonly":true},"protected":{"description":"Whether posts with this resource should be protected.","type":"boolean","context":["edit"],"readonly":true},"public":{"description":"Whether posts of this resource should be shown in the front end of the site.","type":"boolean","context":["view","edit"],"readonly":true},"queryable":{"description":"Whether posts with this resource should be publicly-queryable.","type":"boolean","context":["view","edit"],"readonly":true},"show_in_list":{"description":"Whether to include posts in the edit listing for their post type.","type":"boolean","context":["edit"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"],"readonly":true}}}},"\/wp\/v2\/taxonomies":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"type":{"required":false,"description":"Limit results to resources associated with a specific post type."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"taxonomy","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"show_cloud":{"description":"Whether or not the term cloud should be displayed.","type":"boolean","context":["edit"],"readonly":true},"types":{"description":"Types associated with resource.","type":"array","context":["view","edit"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/taxonomies"}},"\/wp\/v2\/taxonomies\/(?P[\\w-]+)":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"taxonomy","type":"object","properties":{"capabilities":{"description":"All capabilities used by the resource.","type":"array","context":["edit"],"readonly":true},"description":{"description":"A human-readable description of the resource.","type":"string","context":["view","edit"],"readonly":true},"hierarchical":{"description":"Whether or not the resource should have children.","type":"boolean","context":["view","edit"],"readonly":true},"labels":{"description":"Human-readable labels for the resource for various contexts.","type":"object","context":["edit"],"readonly":true},"name":{"description":"The title for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["view","edit","embed"],"readonly":true},"show_cloud":{"description":"Whether or not the term cloud should be displayed.","type":"boolean","context":["edit"],"readonly":true},"types":{"description":"Types associated with resource.","type":"array","context":["view","edit"],"readonly":true}}}},"\/wp\/v2\/categories":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","slug","term_group","description","count"],"description":"Sort collection by resource attribute."},"hide_empty":{"required":false,"default":false,"description":"Whether to hide resources not assigned to any posts."},"parent":{"required":false,"description":"Limit result set to resources assigned to a specific parent."},"post":{"required":false,"description":"Limit result set to resources assigned to a specific post."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"description":{"required":false},"name":{"required":true},"slug":{"required":false},"parent":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"category","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true},"parent":{"description":"The id for the parent of the resource.","type":"integer","context":["view","edit"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/categories"}},"\/wp\/v2\/categories\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"description":{"required":false},"name":{"required":false},"slug":{"required":false},"parent":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"category","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true},"parent":{"description":"The id for the parent of the resource.","type":"integer","context":["view","edit"]}}}},"\/wp\/v2\/tags":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","slug","term_group","description","count"],"description":"Sort collection by resource attribute."},"hide_empty":{"required":false,"default":false,"description":"Whether to hide resources not assigned to any posts."},"post":{"required":false,"description":"Limit result set to resources assigned to a specific post."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"description":{"required":false},"name":{"required":true},"slug":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"tag","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/tags"}},"\/wp\/v2\/tags\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"description":{"required":false},"name":{"required":false},"slug":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"tag","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["view","embed","edit"],"readonly":true},"count":{"description":"Number of published posts for the resource.","type":"integer","context":["view","edit"],"readonly":true},"description":{"description":"HTML description of the resource.","type":"string","context":["view","edit"]},"link":{"description":"URL to the resource.","type":"string","format":"uri","context":["view","embed","edit"],"readonly":true},"name":{"description":"HTML title for the resource.","type":"string","context":["view","embed","edit"],"required":true},"slug":{"description":"An alphanumeric identifier for the resource unique to its type.","type":"string","context":["view","embed","edit"]},"taxonomy":{"description":"Type attribution for the resource.","type":"string","enum":["category","post_tag","nav_menu","link_category","post_format"],"context":["view","embed","edit"],"readonly":true}}}},"\/wp\/v2\/users":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","registered_date"],"description":"Sort collection by object attribute."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"username":{"required":true},"name":{"required":false},"first_name":{"required":false},"last_name":{"required":false},"email":{"required":true},"url":{"required":false},"description":{"required":false},"nickname":{"required":false},"slug":{"required":false},"roles":{"required":false},"capabilities":{"required":false},"password":{"required":true}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"user","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["embed","view","edit"],"readonly":true},"username":{"description":"Login name for the resource.","type":"string","context":["edit"],"required":true},"name":{"description":"Display name for the resource.","type":"string","context":["embed","view","edit"]},"first_name":{"description":"First name for the resource.","type":"string","context":["edit"]},"last_name":{"description":"Last name for the resource.","type":"string","context":["edit"]},"email":{"description":"The email address for the resource.","type":"string","format":"email","context":["edit"],"required":true},"url":{"description":"URL of the resource.","type":"string","format":"uri","context":["embed","view","edit"]},"description":{"description":"Description of the resource.","type":"string","context":["embed","view","edit"]},"link":{"description":"Author URL to the resource.","type":"string","format":"uri","context":["embed","view","edit"],"readonly":true},"nickname":{"description":"The nickname for the resource.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"]},"registered_date":{"description":"Registration date for the resource.","type":"date-time","context":["edit"],"readonly":true},"roles":{"description":"Roles assigned to the resource.","type":"array","context":["edit"]},"capabilities":{"description":"All capabilities assigned to the resource.","type":"object","context":["edit"]},"extra_capabilities":{"description":"Any extra capabilities assigned to the resource.","type":"object","context":["edit"],"readonly":true},"avatar_urls":{"description":"Avatar URLs for the resource.","type":"object","context":["embed","view","edit"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/users"}},"\/wp\/v2\/users\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"username":{"required":false},"name":{"required":false},"first_name":{"required":false},"last_name":{"required":false},"email":{"required":false},"url":{"required":false},"description":{"required":false},"nickname":{"required":false},"slug":{"required":false},"roles":{"required":false},"capabilities":{"required":false},"password":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."},"reassign":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"user","type":"object","properties":{"id":{"description":"Unique identifier for the resource.","type":"integer","context":["embed","view","edit"],"readonly":true},"username":{"description":"Login name for the resource.","type":"string","context":["edit"],"required":true},"name":{"description":"Display name for the resource.","type":"string","context":["embed","view","edit"]},"first_name":{"description":"First name for the resource.","type":"string","context":["edit"]},"last_name":{"description":"Last name for the resource.","type":"string","context":["edit"]},"email":{"description":"The email address for the resource.","type":"string","format":"email","context":["edit"],"required":true},"url":{"description":"URL of the resource.","type":"string","format":"uri","context":["embed","view","edit"]},"description":{"description":"Description of the resource.","type":"string","context":["embed","view","edit"]},"link":{"description":"Author URL to the resource.","type":"string","format":"uri","context":["embed","view","edit"],"readonly":true},"nickname":{"description":"The nickname for the resource.","type":"string","context":["edit"]},"slug":{"description":"An alphanumeric identifier for the resource.","type":"string","context":["embed","view","edit"]},"registered_date":{"description":"Registration date for the resource.","type":"date-time","context":["edit"],"readonly":true},"roles":{"description":"Roles assigned to the resource.","type":"array","context":["edit"]},"capabilities":{"description":"All capabilities assigned to the resource.","type":"object","context":["edit"]},"extra_capabilities":{"description":"Any extra capabilities assigned to the resource.","type":"object","context":["edit"],"readonly":true},"avatar_urls":{"description":"Avatar URLs for the resource.","type":"object","context":["embed","view","edit"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}}}}},"\/wp\/v2\/users\/me":{"namespace":"wp\/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/users\/me"}},"\/wp\/v2\/comments":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"author":{"required":false,"description":"Limit result set to comments assigned to specific user ids. Requires authorization."},"author_exclude":{"required":false,"description":"Ensure result set excludes comments assigned to specific user ids. Requires authorization."},"author_email":{"required":false,"description":"Limit result set to that from a specific author email. Requires authorization."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"karma":{"required":false,"description":"Limit result set to that of a particular comment karma. Requires authorization."},"offset":{"required":false,"description":"Offset the result set by a specific number of comments."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date_gmt","enum":["date","date_gmt","id","include","post","parent","type"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to resources of specific parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific parent ids."},"post":{"required":false,"default":[],"description":"Limit result set to resources assigned to specific post ids."},"status":{"required":false,"default":"approve","description":"Limit result set to comments assigned a specific status. Requires authorization."},"type":{"required":false,"default":"comment","description":"Limit result set to comments assigned a specific type. Requires authorization."}}},{"methods":["POST"],"args":{"author":{"required":false},"author_email":{"required":false},"author_name":{"required":false,"default":""},"author_url":{"required":false},"content":{"required":false,"default":""},"date":{"required":false},"date_gmt":{"required":false},"karma":{"required":false},"parent":{"required":false,"default":0},"post":{"required":false,"default":0},"status":{"required":false},"type":{"required":false,"default":""}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"comment","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"author":{"description":"The id of the user object, if author was a user.","type":"integer","context":["view","edit","embed"]},"author_avatar_urls":{"description":"Avatar URLs for the object author.","type":"object","context":["view","edit","embed"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}},"author_email":{"description":"Email address for the object author.","type":"string","format":"email","context":["edit"]},"author_ip":{"description":"IP address for the object author.","type":"string","context":["edit"],"readonly":true},"author_name":{"description":"Display name for the object author.","type":"string","context":["view","edit","embed"]},"author_url":{"description":"URL for the object author.","type":"string","format":"uri","context":["view","edit","embed"]},"author_user_agent":{"description":"User agent for the object author.","type":"string","context":["edit"],"readonly":true},"content":{"description":"The content for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published as GMT.","type":"string","format":"date-time","context":["view","edit"]},"karma":{"description":"Karma for the object.","type":"integer","context":["edit"]},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"post":{"description":"The id of the associated post object.","type":"integer","context":["view","edit"]},"status":{"description":"State of the object.","type":"string","context":["view","edit"]},"type":{"description":"Type of Comment for the object.","type":"string","context":["view","edit","embed"]}}},"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/wp\/v2\/comments"}},"\/wp\/v2\/comments\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"author":{"required":false},"author_email":{"required":false},"author_name":{"required":false},"author_url":{"required":false},"content":{"required":false},"date":{"required":false},"date_gmt":{"required":false},"karma":{"required":false},"parent":{"required":false},"post":{"required":false},"status":{"required":false},"type":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"comment","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["view","edit","embed"],"readonly":true},"author":{"description":"The id of the user object, if author was a user.","type":"integer","context":["view","edit","embed"]},"author_avatar_urls":{"description":"Avatar URLs for the object author.","type":"object","context":["view","edit","embed"],"readonly":true,"properties":{"24":{"description":"Avatar URL with image size of 24 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"48":{"description":"Avatar URL with image size of 48 pixels.","type":"string","format":"uri","context":["embed","view","edit"]},"96":{"description":"Avatar URL with image size of 96 pixels.","type":"string","format":"uri","context":["embed","view","edit"]}}},"author_email":{"description":"Email address for the object author.","type":"string","format":"email","context":["edit"]},"author_ip":{"description":"IP address for the object author.","type":"string","context":["edit"],"readonly":true},"author_name":{"description":"Display name for the object author.","type":"string","context":["view","edit","embed"]},"author_url":{"description":"URL for the object author.","type":"string","format":"uri","context":["view","edit","embed"]},"author_user_agent":{"description":"User agent for the object author.","type":"string","context":["edit"],"readonly":true},"content":{"description":"The content for the object.","type":"object","context":["view","edit","embed"],"properties":{"raw":{"description":"Content for the object, as it exists in the database.","type":"string","context":["edit"]},"rendered":{"description":"HTML content for the object, transformed for display.","type":"string","context":["view","edit","embed"]}}},"date":{"description":"The date the object was published.","type":"string","format":"date-time","context":["view","edit","embed"]},"date_gmt":{"description":"The date the object was published as GMT.","type":"string","format":"date-time","context":["view","edit"]},"karma":{"description":"Karma for the object.","type":"integer","context":["edit"]},"link":{"description":"URL to the object.","type":"string","format":"uri","context":["view","edit","embed"],"readonly":true},"parent":{"description":"The id for the parent of the object.","type":"integer","context":["view","edit","embed"]},"post":{"description":"The id of the associated post object.","type":"integer","context":["view","edit"]},"status":{"description":"State of the object.","type":"string","context":["view","edit"]},"type":{"description":"Type of Comment for the object.","type":"string","context":["view","edit","embed"]}}}},"\/oembed\/1.0":{"namespace":"oembed\/1.0","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"namespace":{"required":false,"default":"oembed\/1.0"},"context":{"required":false,"default":"view"}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/oembed\/1.0"}},"\/oembed\/1.0\/embed":{"namespace":"oembed\/1.0","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"url":{"required":true},"format":{"required":false,"default":"json"},"maxwidth":{"required":false,"default":600}}}],"_links":{"self":"http:\/\/wpapi.loc\/wp-json\/oembed\/1.0\/embed"}},"\/wp\/v2\/posts\/(?P[\\d]+)\/meta":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST"],"args":{"key":{"required":true},"value":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}},"\/wp\/v2\/posts\/(?P[\\d]+)\/meta\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"key":{"required":false},"value":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/meta":{"namespace":"wp\/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST"],"args":{"key":{"required":true},"value":{"required":false}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}},"\/wp\/v2\/pages\/(?P[\\d]+)\/meta\/(?P[\\d]+)":{"namespace":"wp\/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"edit","enum":["edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"key":{"required":false},"value":{"required":false}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}],"schema":{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","title":"meta","type":"object","properties":{"id":{"description":"Unique identifier for the object.","type":"integer","context":["edit"],"readonly":true},"key":{"description":"The key for the custom field.","type":"string","context":["edit"],"required":true},"value":{"description":"The value of the custom field.","type":"string","context":["edit"]}}}}},"_links":{"help":[{"href":"http:\/\/v2.wp-api.org\/"}]}} +{"name":"WP-API Testbed","description":"Just another WordPress site","url":"http://wpapi.loc/wp","home":"http://wpapi.loc","namespaces":["wp/v2","oembed/1.0"],"authentication":{"oauth1":{"request":"http://wpapi.loc/oauth1/request","authorize":"http://wpapi.loc/oauth1/authorize","access":"http://wpapi.loc/oauth1/access","version":"0.1"}},"routes":{"/":{"namespace":"","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view"}}}],"_links":{"self":"http://wpapi.loc/wp-json/"}},"/wp/v2":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"namespace":{"required":false,"default":"wp/v2"},"context":{"required":false,"default":"view"}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2"}},"/wp/v2/posts":{"namespace":"wp/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"after":{"required":false,"description":"Limit response to resources published after a given ISO8601 compliant date."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"before":{"required":false,"description":"Limit response to resources published before a given ISO8601 compliant date."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug"],"description":"Sort collection by object attribute."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"publish","description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."},"categories":{"required":false,"default":[],"description":"Limit result set to all items that have the specified term assigned in the categories taxonomy."},"tags":{"required":false,"default":[],"description":"Limit result set to all items that have the specified term assigned in the tags taxonomy."}}},{"methods":["POST"],"args":{"date":{"required":false,"description":"The date the object was published, in the site's timezone."},"date_gmt":{"required":false,"description":"The date the object was published, as GMT."},"password":{"required":false,"description":"A password to protect access to the post."},"slug":{"required":false,"description":"An alphanumeric identifier for the object unique to its type."},"status":{"required":false,"enum":["publish","future","draft","pending","private"],"description":"A named status for the object."},"title":{"required":false,"description":"The title for the object."},"content":{"required":false,"description":"The content for the object."},"author":{"required":false,"description":"The id for the author of the object."},"excerpt":{"required":false,"description":"The excerpt for the object."},"featured_media":{"required":false,"description":"The id of the featured media for the object."},"comment_status":{"required":false,"enum":["open","closed"],"description":"Whether or not comments are open on the object."},"ping_status":{"required":false,"enum":["open","closed"],"description":"Whether or not the object can be pinged."},"format":{"required":false,"enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"],"description":"The format for the object."},"sticky":{"required":false,"description":"Whether or not the object should be treated as sticky."},"categories":{"required":false,"description":"The terms assigned to the object in the category taxonomy."},"tags":{"required":false,"description":"The terms assigned to the object in the post_tag taxonomy."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/posts"}},"/wp/v2/posts/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false,"description":"The date the object was published, in the site's timezone."},"date_gmt":{"required":false,"description":"The date the object was published, as GMT."},"password":{"required":false,"description":"A password to protect access to the post."},"slug":{"required":false,"description":"An alphanumeric identifier for the object unique to its type."},"status":{"required":false,"enum":["publish","future","draft","pending","private"],"description":"A named status for the object."},"title":{"required":false,"description":"The title for the object."},"content":{"required":false,"description":"The content for the object."},"author":{"required":false,"description":"The id for the author of the object."},"excerpt":{"required":false,"description":"The excerpt for the object."},"featured_media":{"required":false,"description":"The id of the featured media for the object."},"comment_status":{"required":false,"enum":["open","closed"],"description":"Whether or not comments are open on the object."},"ping_status":{"required":false,"enum":["open","closed"],"description":"Whether or not the object can be pinged."},"format":{"required":false,"enum":["standard","aside","chat","gallery","link","image","quote","status","video","audio"],"description":"The format for the object."},"sticky":{"required":false,"description":"Whether or not the object should be treated as sticky."},"categories":{"required":false,"description":"The terms assigned to the object in the category taxonomy."},"tags":{"required":false,"description":"The terms assigned to the object in the post_tag taxonomy."}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}]},"/wp/v2/posts/(?P[\\d]+)/revisions":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}]},"/wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["DELETE"],"args":[]}]},"/wp/v2/pages":{"namespace":"wp/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"after":{"required":false,"description":"Limit response to resources published after a given ISO8601 compliant date."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"before":{"required":false,"description":"Limit response to resources published before a given ISO8601 compliant date."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"menu_order":{"required":false,"description":"Limit result set to resources with a specific menu_order value."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug","menu_order"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to those of particular parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Limit result set to all items except those of a particular parent id."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"publish","description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."}}},{"methods":["POST"],"args":{"date":{"required":false,"description":"The date the object was published, in the site's timezone."},"date_gmt":{"required":false,"description":"The date the object was published, as GMT."},"password":{"required":false,"description":"A password to protect access to the post."},"slug":{"required":false,"description":"An alphanumeric identifier for the object unique to its type."},"status":{"required":false,"enum":["publish","future","draft","pending","private"],"description":"A named status for the object."},"parent":{"required":false,"description":"The id for the parent of the object."},"title":{"required":false,"description":"The title for the object."},"content":{"required":false,"description":"The content for the object."},"author":{"required":false,"description":"The id for the author of the object."},"excerpt":{"required":false,"description":"The excerpt for the object."},"featured_media":{"required":false,"description":"The id of the featured media for the object."},"comment_status":{"required":false,"enum":["open","closed"],"description":"Whether or not comments are open on the object."},"ping_status":{"required":false,"enum":["open","closed"],"description":"Whether or not the object can be pinged."},"menu_order":{"required":false,"description":"The order of the object in relation to other object of its type."},"template":{"required":false,"enum":[],"description":"The theme file to use to display the object."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/pages"}},"/wp/v2/pages/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false,"description":"The date the object was published, in the site's timezone."},"date_gmt":{"required":false,"description":"The date the object was published, as GMT."},"password":{"required":false,"description":"A password to protect access to the post."},"slug":{"required":false,"description":"An alphanumeric identifier for the object unique to its type."},"status":{"required":false,"enum":["publish","future","draft","pending","private"],"description":"A named status for the object."},"parent":{"required":false,"description":"The id for the parent of the object."},"title":{"required":false,"description":"The title for the object."},"content":{"required":false,"description":"The content for the object."},"author":{"required":false,"description":"The id for the author of the object."},"excerpt":{"required":false,"description":"The excerpt for the object."},"featured_media":{"required":false,"description":"The id of the featured media for the object."},"comment_status":{"required":false,"enum":["open","closed"],"description":"Whether or not comments are open on the object."},"ping_status":{"required":false,"enum":["open","closed"],"description":"Whether or not the object can be pinged."},"menu_order":{"required":false,"description":"The order of the object in relation to other object of its type."},"template":{"required":false,"enum":[],"description":"The theme file to use to display the object."}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}]},"/wp/v2/pages/(?P[\\d]+)/revisions":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}]},"/wp/v2/pages/(?P[\\d]+)/revisions/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["DELETE"],"args":[]}]},"/wp/v2/media":{"namespace":"wp/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"after":{"required":false,"description":"Limit response to resources published after a given ISO8601 compliant date."},"author":{"required":false,"default":[],"description":"Limit result set to posts assigned to specific authors."},"author_exclude":{"required":false,"default":[],"description":"Ensure result set excludes posts assigned to specific authors."},"before":{"required":false,"description":"Limit response to resources published before a given ISO8601 compliant date."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"desc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date","enum":["date","id","include","title","slug"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to those of particular parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Limit result set to all items except those of a particular parent id."},"slug":{"required":false,"description":"Limit result set to posts with a specific slug."},"status":{"required":false,"default":"inherit","enum":["inherit","private","trash"],"description":"Limit result set to posts assigned a specific status."},"filter":{"required":false,"description":"Use WP Query arguments to modify the response; private query vars require appropriate authorization."},"media_type":{"required":false,"enum":["image","video","text","application","audio"],"description":"Limit result set to attachments of a particular media type."},"mime_type":{"required":false,"description":"Limit result set to attachments of a particular mime type."}}},{"methods":["POST"],"args":{"date":{"required":false,"description":"The date the object was published, in the site's timezone."},"date_gmt":{"required":false,"description":"The date the object was published, as GMT."},"password":{"required":false,"description":"A password to protect access to the post."},"slug":{"required":false,"description":"An alphanumeric identifier for the object unique to its type."},"status":{"required":false,"enum":["publish","future","draft","pending","private"],"description":"A named status for the object."},"title":{"required":false,"description":"The title for the object."},"author":{"required":false,"description":"The id for the author of the object."},"comment_status":{"required":false,"enum":["open","closed"],"description":"Whether or not comments are open on the object."},"ping_status":{"required":false,"enum":["open","closed"],"description":"Whether or not the object can be pinged."},"alt_text":{"required":false,"description":"Alternative text to display when resource is not displayed."},"caption":{"required":false,"description":"The caption for the resource."},"description":{"required":false,"description":"The description for the resource."},"post":{"required":false,"description":"The id for the associated post of the resource."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/media"}},"/wp/v2/media/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"date":{"required":false,"description":"The date the object was published, in the site's timezone."},"date_gmt":{"required":false,"description":"The date the object was published, as GMT."},"password":{"required":false,"description":"A password to protect access to the post."},"slug":{"required":false,"description":"An alphanumeric identifier for the object unique to its type."},"status":{"required":false,"enum":["publish","future","draft","pending","private"],"description":"A named status for the object."},"title":{"required":false,"description":"The title for the object."},"author":{"required":false,"description":"The id for the author of the object."},"comment_status":{"required":false,"enum":["open","closed"],"description":"Whether or not comments are open on the object."},"ping_status":{"required":false,"enum":["open","closed"],"description":"Whether or not the object can be pinged."},"alt_text":{"required":false,"description":"Alternative text to display when resource is not displayed."},"caption":{"required":false,"description":"The caption for the resource."},"description":{"required":false,"description":"The description for the resource."},"post":{"required":false,"description":"The id for the associated post of the resource."}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}]},"/wp/v2/types":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/types"}},"/wp/v2/types/(?P[\\w-]+)":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}]},"/wp/v2/statuses":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/statuses"}},"/wp/v2/statuses/(?P[\\w-]+)":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}]},"/wp/v2/taxonomies":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"type":{"required":false,"description":"Limit results to resources associated with a specific post type."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/taxonomies"}},"/wp/v2/taxonomies/(?P[\\w-]+)":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}}]},"/wp/v2/categories":{"namespace":"wp/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","slug","term_group","description","count"],"description":"Sort collection by resource attribute."},"hide_empty":{"required":false,"default":false,"description":"Whether to hide resources not assigned to any posts."},"parent":{"required":false,"description":"Limit result set to resources assigned to a specific parent."},"post":{"required":false,"description":"Limit result set to resources assigned to a specific post."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"description":{"required":false,"description":"HTML description of the resource."},"name":{"required":true,"description":"HTML title for the resource."},"slug":{"required":false,"description":"An alphanumeric identifier for the resource unique to its type."},"parent":{"required":false,"description":"The id for the parent of the resource."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/categories"}},"/wp/v2/categories/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"description":{"required":false,"description":"HTML description of the resource."},"name":{"required":false,"description":"HTML title for the resource."},"slug":{"required":false,"description":"An alphanumeric identifier for the resource unique to its type."},"parent":{"required":false,"description":"The id for the parent of the resource."}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}]},"/wp/v2/tags":{"namespace":"wp/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","slug","term_group","description","count"],"description":"Sort collection by resource attribute."},"hide_empty":{"required":false,"default":false,"description":"Whether to hide resources not assigned to any posts."},"post":{"required":false,"description":"Limit result set to resources assigned to a specific post."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."}}},{"methods":["POST"],"args":{"description":{"required":false,"description":"HTML description of the resource."},"name":{"required":true,"description":"HTML title for the resource."},"slug":{"required":false,"description":"An alphanumeric identifier for the resource unique to its type."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/tags"}},"/wp/v2/tags/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"description":{"required":false,"description":"HTML description of the resource."},"name":{"required":false,"description":"HTML title for the resource."},"slug":{"required":false,"description":"An alphanumeric identifier for the resource unique to its type."}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."}}}]},"/wp/v2/users":{"namespace":"wp/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"offset":{"required":false,"description":"Offset the result set by a specific number of items."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"name","enum":["id","include","name","registered_date"],"description":"Sort collection by object attribute."},"slug":{"required":false,"description":"Limit result set to resources with a specific slug."},"roles":{"required":false,"description":"Limit result set to resources matching at least one specific role provided. Accepts csv list or single role."}}},{"methods":["POST"],"args":{"username":{"required":true,"description":"Login name for the resource."},"name":{"required":false,"description":"Display name for the resource."},"first_name":{"required":false,"description":"First name for the resource."},"last_name":{"required":false,"description":"Last name for the resource."},"email":{"required":true,"description":"The email address for the resource."},"url":{"required":false,"description":"URL of the resource."},"description":{"required":false,"description":"Description of the resource."},"nickname":{"required":false,"description":"The nickname for the resource."},"slug":{"required":false,"description":"An alphanumeric identifier for the resource."},"roles":{"required":false,"description":"Roles assigned to the resource."},"password":{"required":true,"description":"Password for the resource (never included)."},"capabilities":{"required":false,"description":"All capabilities assigned to the resource."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/users"}},"/wp/v2/users/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"username":{"required":false,"description":"Login name for the resource."},"name":{"required":false,"description":"Display name for the resource."},"first_name":{"required":false,"description":"First name for the resource."},"last_name":{"required":false,"description":"Last name for the resource."},"email":{"required":false,"description":"The email address for the resource."},"url":{"required":false,"description":"URL of the resource."},"description":{"required":false,"description":"Description of the resource."},"nickname":{"required":false,"description":"The nickname for the resource."},"slug":{"required":false,"description":"An alphanumeric identifier for the resource."},"roles":{"required":false,"description":"Roles assigned to the resource."},"password":{"required":false,"description":"Password for the resource (never included)."},"capabilities":{"required":false,"description":"All capabilities assigned to the resource."}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Required to be true, as resource does not support trashing."},"reassign":{"required":false}}}]},"/wp/v2/users/me":{"namespace":"wp/v2","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/users/me"}},"/wp/v2/comments":{"namespace":"wp/v2","methods":["GET","POST"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."},"page":{"required":false,"default":1,"description":"Current page of the collection."},"per_page":{"required":false,"default":10,"description":"Maximum number of items to be returned in result set."},"search":{"required":false,"description":"Limit results to those matching a string."},"after":{"required":false,"description":"Limit response to resources published after a given ISO8601 compliant date."},"author":{"required":false,"description":"Limit result set to comments assigned to specific user ids. Requires authorization."},"author_exclude":{"required":false,"description":"Ensure result set excludes comments assigned to specific user ids. Requires authorization."},"author_email":{"required":false,"description":"Limit result set to that from a specific author email. Requires authorization."},"before":{"required":false,"description":"Limit response to resources published before a given ISO8601 compliant date."},"exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific ids."},"include":{"required":false,"default":[],"description":"Limit result set to specific ids."},"karma":{"required":false,"description":"Limit result set to that of a particular comment karma. Requires authorization."},"offset":{"required":false,"description":"Offset the result set by a specific number of comments."},"order":{"required":false,"default":"asc","enum":["asc","desc"],"description":"Order sort attribute ascending or descending."},"orderby":{"required":false,"default":"date_gmt","enum":["date","date_gmt","id","include","post","parent","type"],"description":"Sort collection by object attribute."},"parent":{"required":false,"default":[],"description":"Limit result set to resources of specific parent ids."},"parent_exclude":{"required":false,"default":[],"description":"Ensure result set excludes specific parent ids."},"post":{"required":false,"default":[],"description":"Limit result set to resources assigned to specific post ids."},"status":{"required":false,"default":"approve","description":"Limit result set to comments assigned a specific status. Requires authorization."},"type":{"required":false,"default":"comment","description":"Limit result set to comments assigned a specific type. Requires authorization."}}},{"methods":["POST"],"args":{"author":{"required":false,"description":"The id of the user object, if author was a user."},"author_email":{"required":false,"description":"Email address for the object author."},"author_name":{"required":false,"default":"","description":"Display name for the object author."},"author_url":{"required":false,"description":"URL for the object author."},"content":{"required":false,"default":"","description":"The content for the object."},"date":{"required":false,"description":"The date the object was published."},"date_gmt":{"required":false,"description":"The date the object was published as GMT."},"karma":{"required":false,"description":"Karma for the object."},"parent":{"required":false,"default":0,"description":"The id for the parent of the object."},"post":{"required":false,"default":0,"description":"The id of the associated post object."},"status":{"required":false,"description":"State of the object."},"type":{"required":false,"default":"","description":"Type of Comment for the object."}}}],"_links":{"self":"http://wpapi.loc/wp-json/wp/v2/comments"}},"/wp/v2/comments/(?P[\\d]+)":{"namespace":"wp/v2","methods":["GET","POST","PUT","PATCH","DELETE"],"endpoints":[{"methods":["GET"],"args":{"context":{"required":false,"default":"view","enum":["view","embed","edit"],"description":"Scope under which the request is made; determines fields present in response."}}},{"methods":["POST","PUT","PATCH"],"args":{"author":{"required":false,"description":"The id of the user object, if author was a user."},"author_email":{"required":false,"description":"Email address for the object author."},"author_name":{"required":false,"description":"Display name for the object author."},"author_url":{"required":false,"description":"URL for the object author."},"content":{"required":false,"description":"The content for the object."},"date":{"required":false,"description":"The date the object was published."},"date_gmt":{"required":false,"description":"The date the object was published as GMT."},"karma":{"required":false,"description":"Karma for the object."},"parent":{"required":false,"description":"The id for the parent of the object."},"post":{"required":false,"description":"The id of the associated post object."},"status":{"required":false,"description":"State of the object."},"type":{"required":false,"description":"Type of Comment for the object."}}},{"methods":["DELETE"],"args":{"force":{"required":false,"default":false,"description":"Whether to bypass trash and force deletion."}}}]},"/oembed/1.0":{"namespace":"oembed/1.0","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"namespace":{"required":false,"default":"oembed/1.0"},"context":{"required":false,"default":"view"}}}],"_links":{"self":"http://wpapi.loc/wp-json/oembed/1.0"}},"/oembed/1.0/embed":{"namespace":"oembed/1.0","methods":["GET"],"endpoints":[{"methods":["GET"],"args":{"url":{"required":true},"format":{"required":false,"default":"json"},"maxwidth":{"required":false,"default":600}}}],"_links":{"self":"http://wpapi.loc/wp-json/oembed/1.0/embed"}}},"_links":{"help":[{"href":"http://v2.wp-api.org/"}]}} diff --git a/lib/util/log-obj.js b/lib/util/log-obj.js new file mode 100644 index 00000000..e189bf4f --- /dev/null +++ b/lib/util/log-obj.js @@ -0,0 +1,10 @@ +'use strict'; + +var inspect = require( 'util' ).inspect; + +module.exports = function( obj ) { + console.log( inspect( obj, { + colors: true, + depth: null + }) ); +}; diff --git a/lib/util/make-endpoint-request.js b/lib/util/make-endpoint-request.js new file mode 100644 index 00000000..3f406dab --- /dev/null +++ b/lib/util/make-endpoint-request.js @@ -0,0 +1,30 @@ +'use strict'; + +var inherit = require( 'util' ).inherits; +var CollectionRequest = require( '../shared/collection-request' ); + +function makeEndpointRequest( handlerSpec, resource, namespace ) { + + // Create the constructor function for this endpoint + function EndpointRequest( options ) { + this._options = options || {}; + + this._levels = handlerSpec._levels; + this._path = {}; + + // Configure handler for this endpoint + this + .setPathPart( 0, resource ) + .namespace( namespace ); + } + + inherit( EndpointRequest, CollectionRequest ); + + Object.keys( handlerSpec._setters ).forEach(function( setterFnName ) { + EndpointRequest.prototype[ setterFnName ] = handlerSpec._setters[ setterFnName ]; + }); + + return EndpointRequest; +} + +module.exports = makeEndpointRequest; diff --git a/lib/util/parse-route-string.js b/lib/util/parse-route-string.js index ba897cd8..90344b3f 100644 --- a/lib/util/parse-route-string.js +++ b/lib/util/parse-route-string.js @@ -3,146 +3,29 @@ * Take a WP route string (with PCRE named capture groups), such as * @module parseRouteString */ -var util = require( 'util' ); -var _ = require( 'lodash' ); -// function logFull( obj ) { -// console.log( util.inspect( obj, { -// colors: true, -// depth: null -// }) ); -// } - -var CollectionRequest = require( '../shared/collection-request' ); -var inherit = util.inherits; var extend = require( 'node.extend' ); -var generatePathPartSetter = require( './generate-path-part-setter' ); - -// All valid routes in API v2 beta 11 -var routes = require( '../data/endpoint-response.json' ).routes; - -var getValues = require( 'lodash' ).values; - -/* - -"/wp/v2/posts": {}, -"/wp/v2/posts/(?P[\\d]+)": {}, -"/wp/v2/posts/(?P[\\d]+)/revisions": {}, -"/wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+)": {}, -"/wp/v2/pages": {}, -"/wp/v2/pages/(?P[\\d]+)": {}, -"/wp/v2/pages/(?P[\\d]+)/revisions": {}, -"/wp/v2/pages/(?P[\\d]+)/revisions/(?P[\\d]+)": {}, +var createResourceHandlerSpec = require( './resource-handler-spec' ).create; +var makeEndpointRequest = require( './make-endpoint-request' ); +/** + * Given an array of route definitions and a specific namespace for those routes, + * recurse through the node tree representing all possible routes within the + * 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 + * @returns {object} A dictionary of endpoint request handler factories */ +function generateEndpointFactories( namespace, routeDefinitions ) { -var buildRouteTree = require( './build-route-tree' ); -var routesByNamespace = buildRouteTree( routes ); - -/* - -I want to deduce the following API from this tree (or one like it): + // Create + return Object.keys( routeDefinitions ).reduce(function( handlers, resource ) { -wp.posts(); /wp/v2/posts -wp.posts().id( 7 ); /wp/v2/posts/7 -wp.posts().id( 7 ).revisions(); /wp/v2/posts/7/revisions -wp.posts().id( 7 ).revisions( 8 ); /wp/v2/posts/7/revisions/8 + var handlerSpec = createResourceHandlerSpec( routeDefinitions[ resource ], resource ); -^ That last one's the tricky one: I can deduce that this parameter is "id", but -that param will already be taken by the post ID, so sub-collections have to be -set up as `.revisions()` to get the collection, and `.revisions( id )` to get a -specific resource. - -*/ - -// Now that our namespace and levels object has been defined, recurse through -// the node tree representing all possible routes within that namespace to -// define the path value setters and corresponding validators for all possible -// variants of each resource's API endpoints - -function addLevelOption( levelsObj, level, obj ) { - levelsObj[ level ] = levelsObj[ level ] || []; - levelsObj[ level ].push( obj ); -} - -function generateEndpointFactories( namespace, routesArr ) { - console.log( routesArr ); - var endpointFactories = Object.keys( routesArr ).reduce(function( handlers, resource ) { - - var handler = { - _path: { - '0': resource - }, - - // A "level" is a level-keyed object representing the valid options for - // one level of the resource URL - _levels: {}, - - // Objects that hold methods and properties which will be copied to - // instances of this endpoint's handler - _setters: {} - }; - - /** - * Walk the tree - * @param {Object} node A node object - * @param {Object} [node.children] An object of child nodes - * // @return {isLeaf} A boolean indicating whether the processed node is a leaf - */ - function extractSetterFromNode( node ) { - var setterFn; - - // For each node, add its handler to the relevant "level" representation - addLevelOption( handler._levels, node.level, _.pick( node, 'validate', 'methods' ) ); - - // First level is set implicitly, no dedicated setter needed - if ( node.level > 0 ) { - - setterFn = generatePathPartSetter( node ); - - node.names.forEach(function( name ) { - // camel-case the setter name - var setterFnName = name - .toLowerCase() - .replace( /_\w/g, function( match ) { - return match.replace( '_', '' ).toUpperCase(); - }); - - // Don't overwrite previously-set methods - if ( ! handler._setters[ setterFnName ] ) { - handler._setters[ setterFnName ] = setterFn; - } - }); - } - - // console.log( node ); - if ( node.children ) { - // Recurse down to this node's children - getValues( node.children ).map( extractSetterFromNode ); - } - } - - // Walk the tree - getValues( routesArr[ resource ] ).map( extractSetterFromNode ); - - // Create the constructor function for this endpoint - function EndpointRequest( options ) { - this._options = options || {}; - - this._levels = handler._levels; - this._path = {}; - - // Configure handler for this endpoint - this - .setPathPart( 0, resource ) - .namespace( namespace ); - } - inherit( EndpointRequest, CollectionRequest ); - - console.log( handler ); - Object.keys( handler._setters ).forEach(function( setterFnName ) { - EndpointRequest.prototype[ setterFnName ] = handler._setters[ setterFnName ]; - }); + var EndpointRequest = makeEndpointRequest( handlerSpec, resource, namespace ); // "handler" object is now fully prepared; create the factory method that // will instantiate and return a handler instance @@ -154,21 +37,6 @@ function generateEndpointFactories( namespace, routesArr ) { return handlers; }, {} ); - - return endpointFactories; } -var WP = generateEndpointFactories( 'wp/v2', routesByNamespace[ 'wp/v2' ] ); -// console.log( -// WP.posts({ -// endpoint: 'http://kadamwhite.com/wp-json' -// }).id( 4 ).revisions( 52 ).validatePath()._renderURI() -// ); -// try { -// WP.posts().revisions( 52 ).validatePath()._renderURI(); -// } catch ( e ) { console.error( e ); } -// console.log( -// WP.posts().id( 4 ).meta().filter( 'some', 'prop' )._renderURI() -// ); - -module.exports = WP; +module.exports = generateEndpointFactories; diff --git a/lib/util/resource-handler-spec.js b/lib/util/resource-handler-spec.js new file mode 100644 index 00000000..d0043c0b --- /dev/null +++ b/lib/util/resource-handler-spec.js @@ -0,0 +1,106 @@ +'use strict'; + +var pick = require( 'lodash' ).pick; +var getValues = require( 'lodash' ).values; +var generatePathPartSetter = require( './generate-path-part-setter' ); + +var logObj = require( './log-obj' ); + +function addLevelOption( levelsObj, level, obj ) { + levelsObj[ level ] = levelsObj[ level ] || []; + levelsObj[ level ].push( obj ); +} + +function assignSetterFnForNode( handler, node ) { + var setterFn; + + // For each node, add its handler to the relevant "level" representation + addLevelOption( handler._levels, node.level, pick( node, 'validate', 'methods' ) ); + + // First level is set implicitly, no dedicated setter needed + if ( node.level > 0 ) { + + setterFn = generatePathPartSetter( node ); + + node.names.forEach(function( name ) { + // camel-case the setter name + var setterFnName = name + .toLowerCase() + .replace( /_\w/g, function( match ) { + return match.replace( '_', '' ).toUpperCase(); + }); + + // Don't overwrite previously-set methods + if ( ! handler._setters[ setterFnName ] ) { + handler._setters[ setterFnName ] = setterFn; + } + }); + } +} + +/** + * Walk the tree of a specific resource node to create the setter methods + * + * + * The API we want to produce from the node tree looks like this: + * + * wp.posts(); /wp/v2/posts + * wp.posts().id( 7 ); /wp/v2/posts/7 + * wp.posts().id( 7 ).revisions(); /wp/v2/posts/7/revisions + * wp.posts().id( 7 ).revisions( 8 ); /wp/v2/posts/7/revisions/8 + * + * ^ That last one's the tricky one: we can deduce that this parameter is "id", but + * that param will already be taken by the post ID, so sub-collections have to be + * set up as `.revisions()` to get the collection, and `.revisions( id )` to get a + * specific resource. + * + * @param {Object} node A node object + * @param {Object} [node.children] An object of child nodes + * // @return {isLeaf} A boolean indicating whether the processed node is a leaf + */ +function extractSetterFromNode( handler, node ) { + + assignSetterFnForNode( handler, node ); + + if ( node.children ) { + // Recurse down to this node's children + getValues( node.children ).map( extractSetterFromNode.bind( null, handler ) ); + } +} + +/** + * Create a node handler specification object from a route definition object + * + * @param {object} routeDefinition A route definition object + * @param {string} resource The string key of the resource for which to create a handler + * @returns {object} A handler spec object with _path, _levels and _setters properties + */ +function createNodeHandlerSpec( routeDefinition, resource ) { + + var handler = { + // A "path" is an ordered set of + _path: { + '0': resource + }, + + // A "level" is a level-keyed object representing the valid options for + // one level of the resource URL + _levels: {}, + + // Objects that hold methods and properties which will be copied to + // instances of this endpoint's handler + _setters: {} + }; + + // Walk the tree + getValues( routeDefinition ).map( extractSetterFromNode.bind( null, handler ) ); + + return handler; +} + +module.exports = { + create: createNodeHandlerSpec, + _extractSetterFromNode: extractSetterFromNode, + _assignSetterFnForNode: assignSetterFnForNode, + _addLevelOption: addLevelOption +}; diff --git a/wp.js b/wp.js index 52362d74..125edfd1 100644 --- a/wp.js +++ b/wp.js @@ -17,9 +17,12 @@ */ var extend = require( 'node.extend' ); +// All valid routes in API v2 beta 11 +var routes = require( './lib/data/endpoint-response.json' ).routes; +var buildRouteTree = require( './lib/util/build-route-tree' ); +var routesByNamespace = buildRouteTree( routes ); var generateEndpointFactories = require( './lib/util/parse-route-string' ); - -var endpointFactories = generateEndpointFactories; +var endpointFactories = generateEndpointFactories( 'wp/v2', routesByNamespace[ 'wp/v2' ] ); console.log( endpointFactories ); @@ -109,7 +112,11 @@ WP.prototype.comments = function( options ) { * @param {Object} [options] An options hash for a new MediaRequest * @return {MediaRequest} A MediaRequest instance */ -WP.prototype.media = endpointFactories.media; +WP.prototype.media = function( options ) { + options = options || {}; + options = extend( options, this._options ); + return new MediaRequest( options ); +}; /** * Start a request against the `/pages` endpoint From f5ce771a46e3da8f6152291947fa3e6939f24fb6 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Wed, 8 Jun 2016 22:28:59 -0500 Subject: [PATCH 19/32] Begin moving code around to test built-route-tree.js --- lib/util/build-route-tree.js | 129 --------------------------------- lib/util/ensure.js | 7 ++ lib/util/route-tree.js | 132 ++++++++++++++++++++++++++++++++++ tests/unit/lib/util/ensure.js | 47 ++++++++++++ wp.js | 2 +- 5 files changed, 187 insertions(+), 130 deletions(-) delete mode 100644 lib/util/build-route-tree.js create mode 100644 lib/util/ensure.js create mode 100644 lib/util/route-tree.js create mode 100644 tests/unit/lib/util/ensure.js diff --git a/lib/util/build-route-tree.js b/lib/util/build-route-tree.js deleted file mode 100644 index 419cf596..00000000 --- a/lib/util/build-route-tree.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict'; - -var namedGroupRegexp = require( './named-group-regexp' ); - -function ensure( obj, prop, propDefaultValue ) { - if ( ! obj[ prop ] ) { - obj[ prop ] = propDefaultValue; - } -} - -function buildRouteTree( routes ) { - return Object.keys( routes ).reduce(function( namespaces, route ) { - var routeObj = routes[ route ]; - var nsForRoute = routeObj.namespace; - - // Strip the namespace from the route string (all routes should have the - // format `/namespace/other/stuff`) @TODO: Validate this assumption - var routeString = route.replace( '/' + nsForRoute + '/', '' ); - var routeComponents = routeString.split( '/' ); - - // Do not make a namespace group for the API root - // Do not add the namespace root to its own group - // Do not take any action if routeString is empty - if ( ! nsForRoute || '/' + nsForRoute === route || ! routeString ) { - return namespaces; - } - - // Ensure that the namespace object for this namespace exists - ensure( namespaces, nsForRoute, {} ); - - // Get a local reference to namespace object - var ns = namespaces[ nsForRoute ]; - - // The first element of the route tells us what type of resource this route - // is for, e.g. "posts" or "comments": we build one handler per resource - // type, so we group like resource paths together. - var resource = routeComponents[0]; - - // @TODO: This code above currently precludes baseless routes, e.g. - // myplugin/v2/(?P\w+) -- should those be supported? - - // Create an array to represent this resource, and ensure it is assigned - // to the namespace object. The array will structure the "levels" (path - // components and subresource types) of this resource's endpoint handler. - ensure( ns, resource, {} ); - var levels = ns[ resource ]; - - routeComponents.reduce(function( parentLevel, component, idx, components ) { - // Check to see if this component is a dynamic URL segment (i.e. defined by - // a named capture group regular expression). namedGroup will be `null` if - // the regexp does not match, or else an array defining the RegExp match, e.g. - // [ - // 'P[\\d]+)', - // 'id', // Name of the group - // '[\\d]+', // regular expression for this URL segment's contents - // index: 15, - // input: '/wp/v2/posts/(?P[\\d]+)' - // ] - var namedGroup = component.match( namedGroupRegexp ); - // Pull out references to the relevant indices of the match, for utility: - // `null` checking is necessary in case the component did not match the RE, - // hence the `namedGroup &&`. - var groupName = namedGroup && namedGroup[ 1 ]; - var groupPattern = namedGroup && namedGroup[ 2 ]; - - // When branching based on a dynamic capture group we used the group's RE - // pattern as the unique identifier: this is done because the same group - // could be assigned different names in different endpoint handlers, e.g. - // "id" for posts/:id vs "parent_id" for posts/:parent_id/revisions. - var levelKey = namedGroup ? groupPattern : component; - - // Level name, on the other hand, would take its value from the group's name - var levelName = namedGroup ? groupName : component; - - // Check whether we have a preexisting node at this level of the tree, and - // create a new level object if not - var currentLevel = parentLevel[ levelKey ] || { - namedGroup: namedGroup ? true : false, - level: idx, - names: [] - }; - - // A level's "name" corresponds to the list of strings which could describe - // an endpoint's component setter functions: "id", "revisions", etc. - if ( currentLevel.names.indexOf( levelName ) < 0 ) { - currentLevel.names.push( levelName ); - } - - // A level's validate method is called to check whether a value being set - // on the request URL is of the proper type for the location in which it - // is specified. If a group pattern was found, the validator checks whether - // the input string exactly matches the group pattern. - var groupPatternRE = groupPattern && new RegExp( '^' + groupPattern + '$' ); - - // Only one validate function is maintained for each node, because each node - // is defined either by a string literal or by a specific regular expression. - currentLevel.validate = function( input ) { - return groupPatternRE ? groupPatternRE.test( input ) : input === component; - }; - - // Check to see whether to expect more nodes within this branch of the tree, - if ( components[ idx + 1 ] ) { - // and create a "children" object to hold those nodes if necessary - currentLevel.children = currentLevel.children || {}; - } else { - // At leaf nodes, specify the method capabilities of this endpoint - currentLevel.methods = routeObj.methods ? routeObj.methods.map(function( str ) { - return str.toLowerCase(); - }) : []; - - // // Label node with the title of this endpoint's resource, if available - // if ( routeObj.schema && routeObj.schema.title ) { - // currentLevel.title = routeObj.schema.title; - // } - } - - // Return the child node object as the new "level" - parentLevel[ levelKey ] = currentLevel; - return currentLevel.children; - }, levels ); - - // namespaces[ nsForRoute ] = levels; - // namespaces[ nsForRoute ].routes.push( routeString ); - - return namespaces; - }, {} ); -} - -module.exports = buildRouteTree; diff --git a/lib/util/ensure.js b/lib/util/ensure.js new file mode 100644 index 00000000..5c2b3c51 --- /dev/null +++ b/lib/util/ensure.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function( obj, prop, propDefaultValue ) { + if ( obj && typeof obj[ prop ] === 'undefined' ) { + obj[ prop ] = propDefaultValue; + } +}; diff --git a/lib/util/route-tree.js b/lib/util/route-tree.js new file mode 100644 index 00000000..83d80ba8 --- /dev/null +++ b/lib/util/route-tree.js @@ -0,0 +1,132 @@ +'use strict'; + +var namedGroupRegexp = require( './named-group-regexp' ); +var ensure = require( './ensure' ); + +function reduceRouteComponents( routeObj, parentLevel, component, idx, components ) { + // Check to see if this component is a dynamic URL segment (i.e. defined by + // a named capture group regular expression). namedGroup will be `null` if + // the regexp does not match, or else an array defining the RegExp match, e.g. + // [ + // 'P[\\d]+)', + // 'id', // Name of the group + // '[\\d]+', // regular expression for this URL segment's contents + // index: 15, + // input: '/wp/v2/posts/(?P[\\d]+)' + // ] + var namedGroup = component.match( namedGroupRegexp ); + // Pull out references to the relevant indices of the match, for utility: + // `null` checking is necessary in case the component did not match the RE, + // hence the `namedGroup &&`. + var groupName = namedGroup && namedGroup[ 1 ]; + var groupPattern = namedGroup && namedGroup[ 2 ]; + + // When branching based on a dynamic capture group we used the group's RE + // pattern as the unique identifier: this is done because the same group + // could be assigned different names in different endpoint handlers, e.g. + // "id" for posts/:id vs "parent_id" for posts/:parent_id/revisions. + var levelKey = namedGroup ? groupPattern : component; + + // Level name, on the other hand, would take its value from the group's name + var levelName = namedGroup ? groupName : component; + + // Check whether we have a preexisting node at this level of the tree, and + // create a new level object if not + var currentLevel = parentLevel[ levelKey ] || { + namedGroup: namedGroup ? true : false, + level: idx, + names: [] + }; + + // A level's "name" corresponds to the list of strings which could describe + // an endpoint's component setter functions: "id", "revisions", etc. + if ( currentLevel.names.indexOf( levelName ) < 0 ) { + currentLevel.names.push( levelName ); + } + + // A level's validate method is called to check whether a value being set + // on the request URL is of the proper type for the location in which it + // is specified. If a group pattern was found, the validator checks whether + // the input string exactly matches the group pattern. + var groupPatternRE = groupPattern && new RegExp( '^' + groupPattern + '$' ); + + // Only one validate function is maintained for each node, because each node + // is defined either by a string literal or by a specific regular expression. + currentLevel.validate = function( input ) { + return groupPatternRE ? groupPatternRE.test( input ) : input === component; + }; + + // Check to see whether to expect more nodes within this branch of the tree, + if ( components[ idx + 1 ] ) { + // and create a "children" object to hold those nodes if necessary + currentLevel.children = currentLevel.children || {}; + } else { + // At leaf nodes, specify the method capabilities of this endpoint + currentLevel.methods = routeObj.methods ? routeObj.methods.map(function( str ) { + return str.toLowerCase(); + }) : []; + + // // Label node with the title of this endpoint's resource, if available + // if ( routeObj.schema && routeObj.schema.title ) { + // currentLevel.title = routeObj.schema.title; + // } + } + + // Return the child node object as the new "level" + parentLevel[ levelKey ] = currentLevel; + return currentLevel.children; +} + +function reduceRouteTree( routes, namespaces, route ) { + var routeObj = routes[ route ]; + var nsForRoute = routeObj.namespace; + + // Strip the namespace from the route string (all routes should have the + // format `/namespace/other/stuff`) @TODO: Validate this assumption + var routeString = route.replace( '/' + nsForRoute + '/', '' ); + var routeComponents = routeString.split( '/' ); + + // Do not make a namespace group for the API root + // Do not add the namespace root to its own group + // Do not take any action if routeString is empty + if ( ! nsForRoute || '/' + nsForRoute === route || ! routeString ) { + return namespaces; + } + + // Ensure that the namespace object for this namespace exists + ensure( namespaces, nsForRoute, {} ); + + // Get a local reference to namespace object + var ns = namespaces[ nsForRoute ]; + + // The first element of the route tells us what type of resource this route + // is for, e.g. "posts" or "comments": we build one handler per resource + // type, so we group like resource paths together. + var resource = routeComponents[0]; + + // @TODO: This code above currently precludes baseless routes, e.g. + // myplugin/v2/(?P\w+) -- should those be supported? + + // Create an array to represent this resource, and ensure it is assigned + // to the namespace object. The array will structure the "levels" (path + // components and subresource types) of this resource's endpoint handler. + ensure( ns, resource, {} ); + var levels = ns[ resource ]; + + routeComponents.reduce( reduceRouteComponents.bind( null, routeObj ), levels ); + + // namespaces[ nsForRoute ] = levels; + // namespaces[ nsForRoute ].routes.push( routeString ); + + return namespaces; +} + +function buildRouteTree( routes ) { + return Object.keys( routes ).reduce( reduceRouteTree.bind( null, routes ), {} ); +} + +module.exports = { + build: buildRouteTree, + _reduceTree: reduceRouteTree, + _reduceComponents: reduceRouteComponents +}; diff --git a/tests/unit/lib/util/ensure.js b/tests/unit/lib/util/ensure.js new file mode 100644 index 00000000..7512ce4c --- /dev/null +++ b/tests/unit/lib/util/ensure.js @@ -0,0 +1,47 @@ +'use strict'; +var expect = require( 'chai' ).expect; + +var ensure = require( '../../../../lib/util/ensure' ); + +describe( 'ensure utility', function() { + var obj; + + beforeEach(function() { + obj = {}; + }); + + it( 'is defined', function() { + expect( ensure ).to.exist; + }); + + it( 'is a function', function() { + expect( ensure ).to.be.a( 'function' ); + }); + + it( 'sets a default property value on an object', function() { + expect( obj ).not.to.have.property( 'foo' ); + ensure( obj, 'foo', 'bar' ); + expect( obj ).to.have.property( 'foo' ); + expect( obj.foo ).to.be.a( 'string' ); + expect( obj.foo ).to.equal( 'bar' ); + }); + + it( 'will not overwrite an existing value on an object', function() { + obj.foo = 'baz'; + expect( obj ).to.have.property( 'foo' ); + ensure( obj, 'foo', 'bar' ); + expect( obj ).to.have.property( 'foo' ); + expect( obj.foo ).to.be.a( 'string' ); + expect( obj.foo ).to.equal( 'baz' ); + }); + + it( 'will not overwrite a falsy value on an object', function() { + obj.foo = 0; + expect( obj ).to.have.property( 'foo' ); + ensure( obj, 'foo', 'bar' ); + expect( obj ).to.have.property( 'foo' ); + expect( obj.foo ).to.be.a( 'number' ); + expect( obj.foo ).to.equal( 0 ); + }); + +}); diff --git a/wp.js b/wp.js index 125edfd1..42186162 100644 --- a/wp.js +++ b/wp.js @@ -19,7 +19,7 @@ var extend = require( 'node.extend' ); // All valid routes in API v2 beta 11 var routes = require( './lib/data/endpoint-response.json' ).routes; -var buildRouteTree = require( './lib/util/build-route-tree' ); +var buildRouteTree = require( './lib/util/route-tree' ).build; var routesByNamespace = buildRouteTree( routes ); var generateEndpointFactories = require( './lib/util/parse-route-string' ); var endpointFactories = generateEndpointFactories( 'wp/v2', routesByNamespace[ 'wp/v2' ] ); From 12a208bf540efe7314c0eb1d9d053450e1e35341 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 9 Jun 2016 08:59:18 -0500 Subject: [PATCH 20/32] Test route-tree.js, begin code cleanup, add _getArgs - Add _getArgs to route definition object to support automatically setting route query parameter mixins - Remove trivial lodash dependencies from resource-handler-spec - Add basic unit tests to ensure route-tree.build is behaving properly --- lib/util/parse-route-string.js | 2 +- lib/util/resource-handler-spec.js | 27 +++++--- lib/util/route-tree.js | 59 ++++++++++++++--- tests/unit/lib/util/route-tree.js | 101 ++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 tests/unit/lib/util/route-tree.js diff --git a/lib/util/parse-route-string.js b/lib/util/parse-route-string.js index 90344b3f..69725bfb 100644 --- a/lib/util/parse-route-string.js +++ b/lib/util/parse-route-string.js @@ -14,7 +14,7 @@ var makeEndpointRequest = require( './make-endpoint-request' ); * 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 {string} namespace The namespace string for these routes * @param {object} routeDefinitions A dictionary of route definitions from buildRouteTree * @returns {object} A dictionary of endpoint request handler factories */ diff --git a/lib/util/resource-handler-spec.js b/lib/util/resource-handler-spec.js index d0043c0b..ee6491a7 100644 --- a/lib/util/resource-handler-spec.js +++ b/lib/util/resource-handler-spec.js @@ -1,11 +1,7 @@ 'use strict'; -var pick = require( 'lodash' ).pick; -var getValues = require( 'lodash' ).values; var generatePathPartSetter = require( './generate-path-part-setter' ); -var logObj = require( './log-obj' ); - function addLevelOption( levelsObj, level, obj ) { levelsObj[ level ] = levelsObj[ level ] || []; levelsObj[ level ].push( obj ); @@ -15,7 +11,10 @@ function assignSetterFnForNode( handler, node ) { var setterFn; // For each node, add its handler to the relevant "level" representation - addLevelOption( handler._levels, node.level, pick( node, 'validate', 'methods' ) ); + addLevelOption( handler._levels, node.level, { + validate: node.validate, + methods: node.methods + }); // First level is set implicitly, no dedicated setter needed if ( node.level > 0 ) { @@ -41,7 +40,6 @@ function assignSetterFnForNode( handler, node ) { /** * Walk the tree of a specific resource node to create the setter methods * - * * The API we want to produce from the node tree looks like this: * * wp.posts(); /wp/v2/posts @@ -64,7 +62,9 @@ function extractSetterFromNode( handler, node ) { if ( node.children ) { // Recurse down to this node's children - getValues( node.children ).map( extractSetterFromNode.bind( null, handler ) ); + Object.keys( node.children ).forEach(function( key ) { + extractSetterFromNode( handler, node.children[ key ] ); + }); } } @@ -89,11 +89,20 @@ function createNodeHandlerSpec( routeDefinition, resource ) { // Objects that hold methods and properties which will be copied to // instances of this endpoint's handler - _setters: {} + _setters: {}, + + // Arguments (query parameters) that may be set in GET requests to endpoints + // nested within this resource route tree, used to determine the mixins to + // add to the request handler + _getArgs: routeDefinition._getArgs }; // Walk the tree - getValues( routeDefinition ).map( extractSetterFromNode.bind( null, handler ) ); + Object.keys( routeDefinition ).forEach(function( routeDefProp ) { + if ( routeDefProp !== '_getArgs' ) { + extractSetterFromNode( handler, routeDefinition[ routeDefProp ] ); + } + }); return handler; } diff --git a/lib/util/route-tree.js b/lib/util/route-tree.js index 83d80ba8..6ba8f989 100644 --- a/lib/util/route-tree.js +++ b/lib/util/route-tree.js @@ -3,7 +3,22 @@ var namedGroupRegexp = require( './named-group-regexp' ); var ensure = require( './ensure' ); -function reduceRouteComponents( routeObj, parentLevel, component, idx, components ) { +/** + * Method to use when reducing route components array. + * + * @method _reduceRouteComponents + * @private + * @param {object} routeObj A route definition object (set via .bind partial application) + * @param {object} topLevel The top-level route tree object for this set of routes (set + * via .bind partial application) + * @param {object} parentLevel The memo object, which is mutated as the reducer adds + * a new level handler for each level in the route + * @param {string} component The string defining this route component + * @param {number} idx The index of this component within the components array + * @param {string[]} components The array of all components + * @returns {object} The child object of the level being reduced + */ +function reduceRouteComponents( routeObj, topLevel, parentLevel, component, idx, components ) { // Check to see if this component is a dynamic URL segment (i.e. defined by // a named capture group regular expression). namedGroup will be `null` if // the regexp does not match, or else an array defining the RegExp match, e.g. @@ -66,6 +81,26 @@ function reduceRouteComponents( routeObj, parentLevel, component, idx, component return str.toLowerCase(); }) : []; + // At leaf nodes also flag (at the top level) what arguments are + // available to GET requests, so that we may automatically apply the + // appropriate parameter mixins + if ( routeObj.endpoints ) { + topLevel._getArgs = topLevel._getArgs || {}; + routeObj.endpoints.forEach(function( endpoint ) { + // endpoint.methods will be an array of methods like `[ 'GET' ]`: we + // only care about GET for this exercise. Validating POST and PUT args + // could be useful but is currently deemed to be out-of-scope. + endpoint.methods.forEach(function( method ) { + if ( method.toLowerCase() === 'get' ) { + Object.keys( endpoint.args ).forEach(function( argKey ) { + // For each argument, store whether it is required or not + topLevel._getArgs[ argKey ] = endpoint.args[ argKey ].required; + }); + } + }); + }); + } + // // Label node with the title of this endpoint's resource, if available // if ( routeObj.schema && routeObj.schema.title ) { // currentLevel.title = routeObj.schema.title; @@ -77,6 +112,16 @@ function reduceRouteComponents( routeObj, parentLevel, component, idx, component return currentLevel.children; } +/** + * + * @method _reduceRouteTree + * @private + * @param {object[]} routes An array of route objects (set via .bind partial application) + * @param {object} namespaces The memo object that becomes a dictionary mapping API + * namespaces to an object of the namespace's routes + * @param {string} route The string key of a route in `routes` + * @returns {object} The namespaces dictionary memo object + */ function reduceRouteTree( routes, namespaces, route ) { var routeObj = routes[ route ]; var nsForRoute = routeObj.namespace; @@ -113,10 +158,10 @@ function reduceRouteTree( routes, namespaces, route ) { ensure( ns, resource, {} ); var levels = ns[ resource ]; - routeComponents.reduce( reduceRouteComponents.bind( null, routeObj ), levels ); - - // namespaces[ nsForRoute ] = levels; - // namespaces[ nsForRoute ].routes.push( routeString ); + // Recurse through the route components, mutating levels with information about + // each child node encountered while walking through the routes tree and what + // arguments (parameters) are available for GET requests to this endpoint. + routeComponents.reduce( reduceRouteComponents.bind( null, routeObj, levels ), levels ); return namespaces; } @@ -126,7 +171,5 @@ function buildRouteTree( routes ) { } module.exports = { - build: buildRouteTree, - _reduceTree: reduceRouteTree, - _reduceComponents: reduceRouteComponents + build: buildRouteTree }; diff --git a/tests/unit/lib/util/route-tree.js b/tests/unit/lib/util/route-tree.js new file mode 100644 index 00000000..6c1900ed --- /dev/null +++ b/tests/unit/lib/util/route-tree.js @@ -0,0 +1,101 @@ +'use strict'; +var expect = require( 'chai' ).expect; + +var routeTree = require( '../../../../lib/util/route-tree' ); +var endpointResponse = require( '../../../../lib/data/endpoint-response.json' ); + +describe.only( 'route-tree utility', function() { + + describe( '.build()', function() { + var tree; + + beforeEach(function() { + tree = routeTree.build( endpointResponse.routes ); + }); + + it( 'returns an object keyed by API namespace', function() { + var keys = Object.keys( tree ).sort(); + expect( keys.length ).to.equal( 2 ); + expect( keys ).to.deep.equal([ 'oembed/1.0', 'wp/v2' ]); + }); + + it( 'includes objects for all default wp/v2 routes', function() { + var routes = Object.keys( tree[ 'wp/v2' ] ).sort(); + expect( routes ).to.have.length( 10 ); + expect( routes.join( ',' ) ).to + .equal( 'categories,comments,media,pages,posts,statuses,tags,taxonomies,types,users' ); + }); + + it( 'includes objects for all default oembed/1.0 routes', function() { + var routes = Object.keys( tree[ 'oembed/1.0' ] ).sort(); + expect( routes ).to.have.length( 1 ); + expect( routes.join( ',' ) ).to.equal( 'embed' ); + }); + + // Inspect the .posts tree as a smoke test for whether parsing the API + // definition object was successful + describe( 'posts resource tree', function() { + var posts; + + beforeEach(function() { + posts = tree[ 'wp/v2' ].posts; + }); + + it ( 'includes a ._getArgs property', function() { + expect( posts ).to.have.property( '_getArgs' ); + expect( posts._getArgs ).to.be.an( 'object' ); + }); + + it ( '._getArgs specifies a list of supported parameters', function() { + expect( posts ).to.have.property( '_getArgs' ); + expect( posts._getArgs ).to.be.an( 'object' ); + expect( posts._getArgs ).to.deep.equal({ + context: false, + page: false, + per_page: false, + search: false, + after: false, + author: false, + author_exclude: false, + before: false, + exclude: false, + include: false, + offset: false, + order: false, + orderby: false, + slug: false, + status: false, + filter: false, + categories: false, + tags: false + }); + }); + + it ( 'includes a .posts property', function() { + expect( posts ).to.have.property( 'posts' ); + expect( posts.posts ).to.be.an( 'object' ); + }); + + // This is a decidedly incomplete smoke test... + // But if this fails, so will everything else! + it ( '.posts defines the top level of a route tree', function() { + var routeTree = posts.posts; + expect( routeTree ).to.have.property( 'level' ); + expect( routeTree.level ).to.equal( 0 ); + expect( routeTree ).to.have.property( 'methods' ); + expect( routeTree.methods ).to.deep.equal([ 'get', 'post' ]); + expect( routeTree ).to.have.property( 'namedGroup' ); + expect( routeTree.namedGroup ).to.equal( false ); + expect( routeTree ).to.have.property( 'names' ); + expect( routeTree.names ).to.deep.equal([ 'posts' ]); + expect( routeTree ).to.have.property( 'validate' ); + expect( routeTree.validate ).to.be.a( 'function' ); + expect( routeTree ).to.have.property( 'children' ); + expect( routeTree.children ).to.be.an( 'object' ); + }); + + }); + + }); + +}); From f62246e73c354ffb83bad9959d3bb299e6a6de5b Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Sun, 12 Jun 2016 20:45:40 -0400 Subject: [PATCH 21/32] Automatically assign mixins and update WPRequest path validation Mixins are now automatically assigned based on registered _getArgs for a given route tree, rather than being manually mixed in to a route handler. WPRequet logic has been updated to leverage the new _levels-based path validation functions that are deduced within route-tree.js Begin updating the Comments endpoint tests to handle the new logic --- lib/mixins/index.js | 22 ++++ lib/shared/wp-request.js | 50 ++++++-- lib/util/make-endpoint-request.js | 17 +++ lib/util/named-group-regexp.js | 1 + lib/util/route-tree.js | 3 + tests/unit/lib/comments.js | 70 ++++------- tests/unit/lib/util/route-tree.js | 2 +- wp.js | 190 +++--------------------------- 8 files changed, 123 insertions(+), 232 deletions(-) create mode 100644 lib/mixins/index.js diff --git a/lib/mixins/index.js b/lib/mixins/index.js new file mode 100644 index 00000000..807c8868 --- /dev/null +++ b/lib/mixins/index.js @@ -0,0 +1,22 @@ +/** + * This module defines a mapping between supported GET request query parameter + * arguments and their corresponding mixin, if available. + */ +'use strict'; + +var filterMixins = require( './filters' ); +var parameterMixins = require( './parameters' ); + +// `.context`, `.embed`, and `.edit` (a shortcut for `context(edit, true)`) are +// supported by default in WPRequest, as is the base `.param` method. Any GET +// argument parameters not covered here must be set directly by using `.param`. +module.exports = { + author: { author: parameterMixins.author }, + filter: filterMixins, + page: { page: parameterMixins.page }, + parent: { parent: parameterMixins.parent }, + per_page: { perPage: parameterMixins.perPage }, + post: { forPost: parameterMixins.forPost }, + search: { search: parameterMixins.search }, + slug: { slug: parameterMixins.slug, name: parameterMixins.name } +}; diff --git a/lib/shared/wp-request.js b/lib/shared/wp-request.js index 06614482..dd825b06 100644 --- a/lib/shared/wp-request.js +++ b/lib/shared/wp-request.js @@ -70,17 +70,6 @@ function WPRequest( options ) { * @default {} */ this._path = {}; - - /** - * The URL template that will be used to assemble endpoint paths - * (This will be overwritten by each specific endpoint handler constructor) - * - * @property _template - * @type String - * @private - * @default '' - */ - this._template = ''; } // Private helper methods @@ -222,6 +211,30 @@ function populated( obj ) { }, {}); } +/** + * Assert whether a provided URL component is "valid" by checking it against + * an array of registered path component validator methods for that level of + * the URL path. + * + * @param {object[]} levelDefinitions An array of Level Definition objects + * @param {string} levelContents The URL path string that has been specified + * for use on the provided level + * @returns {boolean} Whether the provided input matches any of the provided + * level validation functions + */ +function validatePathLevel( levelDefinitions, levelContents ) { + // One "level" may have multiple options, as a route tree is a branching + // structure. We consider a level "valid" if the provided levelContents + // match any of the available validators. + return levelDefinitions.reduce(function( anyOptionValid, levelOption ) { + if ( ! levelOption.validate ) { + // If there is no validator function, the level is implicitly valid + return true; + } + return anyOptionValid || levelOption.validate( levelContents ); + }, false ); +} + // Pagination-Related Helpers // ========================== @@ -401,7 +414,22 @@ WPRequest.prototype.validatePath = function() { var valid = true; for ( var level = 0; level < maxLevel; level++ ) { + if ( this._path[ level ] ) { + // Validate the provided path level against all available path validators + if ( ! validatePathLevel( this._levels[ level ], this._path[ level ] ) ) { + throw new Error([ + 'Invalid path component:', + levelContents, + 'does not match any of', + this._levels[ level ].reduce(function( components, levelOption ) { + return components.concat( levelOption ).component; + }, [] ).join( ', ' ), + levelOption.component + ].join( ' ' ) ); + } + + // Add the path value to the array path.push( this._path[ level ] ); } else { path.push( ' ??? ' ); diff --git a/lib/util/make-endpoint-request.js b/lib/util/make-endpoint-request.js index 3f406dab..0b242ace 100644 --- a/lib/util/make-endpoint-request.js +++ b/lib/util/make-endpoint-request.js @@ -2,6 +2,7 @@ var inherit = require( 'util' ).inherits; var CollectionRequest = require( '../shared/collection-request' ); +var mixins = require( '../mixins' ); function makeEndpointRequest( handlerSpec, resource, namespace ) { @@ -20,6 +21,22 @@ function makeEndpointRequest( handlerSpec, resource, namespace ) { inherit( EndpointRequest, CollectionRequest ); + // Mix in all available shortcut methods for GET request query parameters that + // are valid within this endpoint tree + Object.keys( handlerSpec._getArgs ).forEach(function( supportedQueryParam ) { + var mixinsForParam = mixins[ supportedQueryParam ]; + + // Only proceed if there is a mixin available AND the specified mixins will + // not overwrite any previously-set prototype method + if ( mixinsForParam ) { + Object.keys( mixinsForParam ).forEach(function( methodName ) { + if ( ! EndpointRequest.prototype[ methodName ] ) { + EndpointRequest.prototype[ methodName ] = mixinsForParam[ methodName ]; + } + }); + } + }); + Object.keys( handlerSpec._setters ).forEach(function( setterFnName ) { EndpointRequest.prototype[ setterFnName ] = handlerSpec._setters[ setterFnName ]; }); diff --git a/lib/util/named-group-regexp.js b/lib/util/named-group-regexp.js index b3de2749..39b9e6a6 100644 --- a/lib/util/named-group-regexp.js +++ b/lib/util/named-group-regexp.js @@ -1,3 +1,4 @@ +'use strict'; /** * Regular Expression to identify a capture group in PCRE formats diff --git a/lib/util/route-tree.js b/lib/util/route-tree.js index 6ba8f989..a58e4e16 100644 --- a/lib/util/route-tree.js +++ b/lib/util/route-tree.js @@ -71,6 +71,9 @@ function reduceRouteComponents( routeObj, topLevel, parentLevel, component, idx, return groupPatternRE ? groupPatternRE.test( input ) : input === component; }; + // Set the component, so that the validator can throw the appropriate error + currentLevel.component = component; + // Check to see whether to expect more nodes within this branch of the tree, if ( components[ idx + 1 ] ) { // and create a "children" object to hold those nodes if necessary diff --git a/tests/unit/lib/comments.js b/tests/unit/lib/comments.js index be5eb782..f55edf8d 100644 --- a/tests/unit/lib/comments.js +++ b/tests/unit/lib/comments.js @@ -1,26 +1,29 @@ 'use strict'; var expect = require( 'chai' ).expect; +var WP = require( '../../../wp' ); var CommentsRequest = require( '../../../lib/comments' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); -describe( 'wp.comments', function() { +describe.only( 'wp.comments', function() { + var site; + var comments; - describe( 'constructor', function() { - - var comments; + beforeEach(function() { + site = WP.site( '/wp-json' ); + comments = site.comments(); + // comments = new CommentsRequest(); + }); - beforeEach(function() { - comments = new CommentsRequest(); - }); + describe( 'constructor', function() { - it( 'should create a CommentsRequest instance', function() { - expect( comments instanceof CommentsRequest ).to.be.true; + it( 'should create a CollectionRequest instance', function() { + expect( comments instanceof CollectionRequest ).to.be.true; }); it( 'should set any passed-in options', function() { - comments = new CommentsRequest({ + comments = site.comments({ booleanProp: true, strProp: 'Some string' }); @@ -28,15 +31,12 @@ describe( 'wp.comments', function() { expect( comments._options.strProp ).to.equal( 'Some string' ); }); - it( 'should default _options to {}', function() { + it.skip( 'should default _options to {}', function() { expect( comments._options ).to.deep.equal( {} ); }); - it( 'should intitialize instance properties', function() { - expect( comments._path ).to.deep.equal( {} ); - expect( comments._template ).to.equal( 'comments(/:id)' ); - var _supportedMethods = comments._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'get|head|post' ); + it( 'should initialize the base path component', function() { + expect( comments._path ).to.deep.equal( { '0': 'comments' } ); }); it( 'should inherit CommentsRequest from CollectionRequest', function() { @@ -70,29 +70,21 @@ describe( 'wp.comments', function() { describe( 'query methods', function() { - var comments; - - beforeEach(function() { - comments = new CommentsRequest(); - comments._options = { - endpoint: '/wp-json/' - }; - }); - it( 'provides a method to set the ID', function() { expect( comments ).to.have.property( 'id' ); expect( comments.id ).to.be.a( 'function' ); comments.id( 314159 ); - expect( comments._path ).to.have.property( 'id' ); - expect( comments._path.id ).to.equal( 314159 ); + expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/314159' ); }); - it( 'parses ID parameters into integers', function() { + it( 'accepts ID parameters as strings', function() { comments.id( '8' ); - expect( comments._path ).to.have.property( 'id' ); - expect( comments._path.id ).to.equal( 8 ); + expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/8' ); + }); + + it( 'converts ID parameters into integers', function() { comments.id( 4.019 ); - expect( comments._path.id ).to.equal( 4 ); + expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/4' ); }); it( 'should update the supported methods when setting ID', function() { @@ -105,15 +97,6 @@ describe( 'wp.comments', function() { describe( 'URL Generation', function() { - var comments; - - beforeEach(function() { - comments = new CommentsRequest(); - comments._options = { - endpoint: '/wp-json/' - }; - }); - it( 'should create the URL for retrieving all comments', function() { var path = comments._renderURI(); expect( path ).to.equal( '/wp-json/wp/v2/comments' ); @@ -137,11 +120,10 @@ describe( 'wp.comments', function() { }); it( 'should restrict template changes to a single instance', function() { - comments._template = 'path/with/comment/nr/:id'; - var newComments = new CommentsRequest(); - newComments._options.endpoint = 'endpoint/url/'; + comments.id( 2 ); + var newComments = site.comments(); var path = newComments.id( 3 )._renderURI(); - expect( path ).to.equal( 'endpoint/url/wp/v2/comments/3' ); + expect( path ).to.equal( '/wp-json/wp/v2/comments/3' ); }); }); diff --git a/tests/unit/lib/util/route-tree.js b/tests/unit/lib/util/route-tree.js index 6c1900ed..e50cfdb6 100644 --- a/tests/unit/lib/util/route-tree.js +++ b/tests/unit/lib/util/route-tree.js @@ -4,7 +4,7 @@ var expect = require( 'chai' ).expect; var routeTree = require( '../../../../lib/util/route-tree' ); var endpointResponse = require( '../../../../lib/data/endpoint-response.json' ); -describe.only( 'route-tree utility', function() { +describe( 'route-tree utility', function() { describe( '.build()', function() { var tree; diff --git a/wp.js b/wp.js index 42186162..32134acc 100644 --- a/wp.js +++ b/wp.js @@ -1,4 +1,3 @@ -'use strict'; /** * A WP REST API client for Node.js * @@ -15,30 +14,21 @@ * @beta }) */ +'use strict'; + var extend = require( 'node.extend' ); // All valid routes in API v2 beta 11 var routes = require( './lib/data/endpoint-response.json' ).routes; var buildRouteTree = require( './lib/util/route-tree' ).build; -var routesByNamespace = buildRouteTree( routes ); var generateEndpointFactories = require( './lib/util/parse-route-string' ); -var endpointFactories = generateEndpointFactories( 'wp/v2', routesByNamespace[ 'wp/v2' ] ); - -console.log( endpointFactories ); var defaults = { username: '', password: '' }; -// Pull in request module constructors -var CommentsRequest = require( './lib/comments' ); -var MediaRequest = require( './lib/media' ); -var PagesRequest = require( './lib/pages' ); -var PostsRequest = require( './lib/posts' ); -var TaxonomiesRequest = require( './lib/taxonomies' ); -var TypesRequest = require( './lib/types' ); -var UsersRequest = require( './lib/users' ); +// Pull in base module constructors var CollectionRequest = require( './lib/shared/collection-request' ); var WPRequest = require( './lib/shared/wp-request' ); @@ -74,6 +64,15 @@ function WP( options ) { return this; } +// Auto-generate default endpoint factories +var routesByNamespace = buildRouteTree( routes ); +var endpointFactories = generateEndpointFactories( 'wp/v2', routesByNamespace[ 'wp/v2' ] ); + +// Apply all auto-generated endpoint factories to the WP object prototype +Object.keys( endpointFactories ).forEach(function( methodName ) { + WP.prototype[ methodName ] = endpointFactories[ methodName ]; +}); + /** * Convenience method for making a new WP instance * @@ -92,165 +91,6 @@ WP.site = function( endpoint ) { return new WP({ endpoint: endpoint }); }; -/** - * Start a request against the `/comments` endpoint - * - * @method comments - * @param {Object} [options] An options hash for a new CommentsRequest - * @return {CommentsRequest} A CommentsRequest instance - */ -WP.prototype.comments = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new CommentsRequest( options ); -}; - -/** - * Start a request against the `/media` endpoint - * - * @method media - * @param {Object} [options] An options hash for a new MediaRequest - * @return {MediaRequest} A MediaRequest instance - */ -WP.prototype.media = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new MediaRequest( options ); -}; - -/** - * Start a request against the `/pages` endpoint - * - * @method pages - * @param {Object} [options] An options hash for a new PagesRequest - * @return {PagesRequest} A PagesRequest instance - */ -WP.prototype.pages = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new PagesRequest( options ).setPathPart( 0, 'pages' ); -}; - -/** - * Start a request against the `/posts` endpoint - * - * @method posts - * @param {Object} [options] An options hash for a new PostsRequest - * @return {PostsRequest} A PostsRequest instance - */ -WP.prototype.posts = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new PostsRequest( options ).setPathPart( 0, 'posts' ); -}; - -/** - * Start a request for a taxonomy or taxonomy term collection - * - * @method taxonomies - * @param {Object} [options] An options hash for a new TaxonomiesRequest - * @return {TaxonomiesRequest} A TaxonomiesRequest instance - */ -WP.prototype.taxonomies = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new TaxonomiesRequest( options ); -}; - -/** - * Start a request for a specific taxonomy object - * - * It is slightly unintuitive to consider the name of a taxonomy a "term," as is - * needed in order to retrieve the taxonomy object from the .taxonomies() method. - * This convenience method lets you create a `TaxonomiesRequest` object that is - * bound to the provided taxonomy name, without having to utilize the "term" method. - * - * @example - * If your site uses two custom taxonomies, book_genre and book_publisher, before you would - * have had to request these terms using the verbose form: - * - * wp.taxonomies().term( 'book_genre' ) - * wp.taxonomies().term( 'book_publisher' ) - * - * Using `.taxonomy()`, the same query can be achieved much more succinctly: - * - * wp.taxonomy( 'book_genre' ) - * wp.taxonomy( 'book_publisher' ) - * - * @method taxonomy - * @param {String} taxonomyName The name of the taxonomy to request - * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the value of taxonomyName - */ -WP.prototype.taxonomy = function( taxonomyName ) { - var options = extend( {}, this._options ); - return new TaxonomiesRequest( options ).term( taxonomyName ); -}; - -/** - * Request a list of category terms - * - * This is a shortcut method to retrieve the terms for the "category" taxonomy - * - * @example - * These are equivalent: - * - * wp.taxonomies().collection( 'categories' ) - * wp.categories() - * - * @method categories - * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the categories collection - */ -WP.prototype.categories = function() { - var options = extend( {}, this._options ); - return new TaxonomiesRequest( options ).collection( 'categories' ); -}; - -/** - * Request a list of post_tag terms - * - * This is a shortcut method to interact with the collection of terms for the - * "post_tag" taxonomy. - * - * @example - * These are equivalent: - * - * wp.taxonomies().collection( 'tags' ) - * wp.tags() - * - * @method tags - * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the tags collection - */ -WP.prototype.tags = function() { - var options = extend( {}, this._options ); - return new TaxonomiesRequest( options ).collection( 'tags' ); -}; - -/** - * Start a request against the `/types` endpoint - * - * @method types - * @param {Object} [options] An options hash for a new TypesRequest - * @return {TypesRequest} A TypesRequest instance - */ -WP.prototype.types = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new TypesRequest( options ).setPathPart( 0, 'types' ); -}; - -/** - * Start a request against the `/users` endpoint - * - * @method users - * @param {Object} [options] An options hash for a new UsersRequest - * @return {UsersRequest} A UsersRequest instance - */ -WP.prototype.users = function( options ) { - options = options || {}; - options = extend( options, this._options ); - return new UsersRequest( options ).setPathPart( 0, 'users' ); -}; - /** * Generate a request against a completely arbitrary endpoint, with no assumptions about * or mutation of path, filtering, or query parameters. This request is not restricted to @@ -278,15 +118,13 @@ WP.prototype.url = function( url ) { * * @method root * @param {String} [relativePath] An endpoint-relative path to which to bind the request - * @param {Boolean} [collection] Whether to return a CollectionRequest or a vanilla WPRequest * @return {CollectionRequest|WPRequest} A request object */ -WP.prototype.root = function( relativePath, collection ) { +WP.prototype.root = function( relativePath ) { relativePath = relativePath || ''; - collection = collection || false; var options = extend( {}, this._options ); // Request should be - var request = collection ? new CollectionRequest( options ) : new WPRequest( options ); + var request = new CollectionRequest( options ); // Set the path template to the string passed in request._path = { '0': relativePath }; From 2eb49119e7061e7313d88bfe49586aa28eebcdcc Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Mon, 13 Jun 2016 22:48:40 -0400 Subject: [PATCH 22/32] Resolve issues preventing comments suite from passing Finish updating comments unit tests to reflect new methodology --- lib/shared/wp-request.js | 28 +++++----- lib/util/generate-path-part-setter.js | 31 +++++++---- lib/util/make-endpoint-request.js | 16 ++++-- lib/util/resource-handler-spec.js | 1 + lib/util/route-tree.js | 15 +++-- tests/unit/lib/comments.js | 79 +++++++++++++++------------ 6 files changed, 101 insertions(+), 69 deletions(-) diff --git a/lib/shared/wp-request.js b/lib/shared/wp-request.js index dd825b06..fbc25071 100644 --- a/lib/shared/wp-request.js +++ b/lib/shared/wp-request.js @@ -226,13 +226,25 @@ function validatePathLevel( levelDefinitions, levelContents ) { // One "level" may have multiple options, as a route tree is a branching // structure. We consider a level "valid" if the provided levelContents // match any of the available validators. - return levelDefinitions.reduce(function( anyOptionValid, levelOption ) { + var valid = levelDefinitions.reduce(function( anyOptionValid, levelOption ) { if ( ! levelOption.validate ) { // If there is no validator function, the level is implicitly valid return true; } return anyOptionValid || levelOption.validate( levelContents ); }, false ); + + if ( ! valid ) { + throw new Error([ + 'Invalid path component:', + levelContents, + // awkward pluralization support: + 'does not match' + ( levelDefinitions.length > 1 ? ' any of' : '' ), + levelDefinitions.reduce(function( components, levelOption ) { + return components.concat( levelOption.component ); + }, [] ).join( ', ' ) + ].join( ' ' ) ); + } } // Pagination-Related Helpers @@ -413,21 +425,11 @@ WPRequest.prototype.validatePath = function() { var path = []; var valid = true; - for ( var level = 0; level < maxLevel; level++ ) { + for ( var level = 0; level <= maxLevel; level++ ) { if ( this._path[ level ] ) { // Validate the provided path level against all available path validators - if ( ! validatePathLevel( this._levels[ level ], this._path[ level ] ) ) { - throw new Error([ - 'Invalid path component:', - levelContents, - 'does not match any of', - this._levels[ level ].reduce(function( components, levelOption ) { - return components.concat( levelOption ).component; - }, [] ).join( ', ' ), - levelOption.component - ].join( ' ' ) ); - } + validatePathLevel( this._levels[ level ], this._path[ level ] ); // Add the path value to the array path.push( this._path[ level ] ); diff --git a/lib/util/generate-path-part-setter.js b/lib/util/generate-path-part-setter.js index f6d0da35..fef780f8 100644 --- a/lib/util/generate-path-part-setter.js +++ b/lib/util/generate-path-part-setter.js @@ -1,7 +1,5 @@ 'use strict'; -var getValues = require( 'lodash' ).values; - /** * Return a function to set part of the request URL path. * @@ -14,9 +12,19 @@ var getValues = require( 'lodash' ).values; * @returns {[type]} [description] */ function generatePathPartSetter( node ) { - var dynamicChildren = getValues( node.children ).filter(function( childNode ) { - return childNode.namedGroup === true; - }); + // Local references to `node` properties used by returned functions + var nodeLevel = node.level; + var nodeName = node.names[ 0 ]; + var supportedMethods = node.methods; + var dynamicChildren = node.children ? Object.keys( node.children ) + .map(function( key ) { + return node.children[ key ]; + }) + .filter(function( childNode ) { + return childNode.namedGroup === true; + }) : []; + var dynamicChild = dynamicChildren.length === 1 && dynamicChildren[ 0 ]; + var dynamicChildLevel = dynamicChild && dynamicChild.level; if ( node.namedGroup ) { /** @@ -33,7 +41,8 @@ function generatePathPartSetter( node ) { */ return function( val ) { /* jshint validthis:true */ - this.setPathPart( node.level, val ); + this.setPathPart( nodeLevel, val ); + this._supportedMethods = supportedMethods; return this; }; } else { @@ -58,13 +67,13 @@ function generatePathPartSetter( node ) { // If the path part is not a namedGroup, it should have exactly one // entry in the names array: use that as the value for this setter, // as it will usually correspond to a collection endpoint. - this.setPathPart( node.level, node.names[ 0 ] ); + this.setPathPart( nodeLevel, nodeName ); // If this node has exactly one dynamic child, this method may act as - // a setter for that child node - var dynamicChild = dynamicChildren.length === 1 && dynamicChildren[ 0 ]; - if ( typeof val !== 'undefined' && dynamicChild ) { - this.setPathPart( dynamicChild.level, val ); + // a setter for that child node. `dynamicChildLevel` will be falsy if the + // node does not have a child or has multiple children. + if ( typeof val !== 'undefined' && dynamicChildLevel ) { + this.setPathPart( dynamicChildLevel, val ); } return this; }; diff --git a/lib/util/make-endpoint-request.js b/lib/util/make-endpoint-request.js index 0b242ace..84321341 100644 --- a/lib/util/make-endpoint-request.js +++ b/lib/util/make-endpoint-request.js @@ -1,6 +1,7 @@ 'use strict'; var inherit = require( 'util' ).inherits; +var WPRequest = require( '../shared/wp-request' ); var CollectionRequest = require( '../shared/collection-request' ); var mixins = require( '../mixins' ); @@ -8,12 +9,19 @@ function makeEndpointRequest( handlerSpec, resource, namespace ) { // Create the constructor function for this endpoint function EndpointRequest( options ) { - this._options = options || {}; - + WPRequest.call( this, options ); + + /** + * Semi-private instance property specifying the available URL path options + * for this endpoint request handler, keyed by ascending whole numbers. + * + * @property _levels + * @type {object} + * @private + */ this._levels = handlerSpec._levels; - this._path = {}; - // Configure handler for this endpoint + // Configure handler for this endpoint's root URL path & set namespace this .setPathPart( 0, resource ) .namespace( namespace ); diff --git a/lib/util/resource-handler-spec.js b/lib/util/resource-handler-spec.js index ee6491a7..b08aa6ea 100644 --- a/lib/util/resource-handler-spec.js +++ b/lib/util/resource-handler-spec.js @@ -12,6 +12,7 @@ function assignSetterFnForNode( handler, node ) { // For each node, add its handler to the relevant "level" representation addLevelOption( handler._levels, node.level, { + component: node.component, validate: node.validate, methods: node.methods }); diff --git a/lib/util/route-tree.js b/lib/util/route-tree.js index a58e4e16..4a131293 100644 --- a/lib/util/route-tree.js +++ b/lib/util/route-tree.js @@ -59,21 +59,21 @@ function reduceRouteComponents( routeObj, topLevel, parentLevel, component, idx, currentLevel.names.push( levelName ); } + // Set the component, so that the validator can throw the appropriate error + currentLevel.component = component; + // A level's validate method is called to check whether a value being set // on the request URL is of the proper type for the location in which it // is specified. If a group pattern was found, the validator checks whether // the input string exactly matches the group pattern. - var groupPatternRE = groupPattern && new RegExp( '^' + groupPattern + '$' ); + var groupPatternRE = new RegExp( groupPattern ? '^' + groupPattern + '$' : component ); // Only one validate function is maintained for each node, because each node // is defined either by a string literal or by a specific regular expression. currentLevel.validate = function( input ) { - return groupPatternRE ? groupPatternRE.test( input ) : input === component; + return groupPatternRE.test( input ); }; - // Set the component, so that the validator can throw the appropriate error - currentLevel.component = component; - // Check to see whether to expect more nodes within this branch of the tree, if ( components[ idx + 1 ] ) { // and create a "children" object to hold those nodes if necessary @@ -83,6 +83,11 @@ function reduceRouteComponents( routeObj, topLevel, parentLevel, component, idx, currentLevel.methods = routeObj.methods ? routeObj.methods.map(function( str ) { return str.toLowerCase(); }) : []; + // Ensure HEAD is included whenever GET is supported: the API automatically + // adds support for HEAD if you have GET + if ( currentLevel.methods.indexOf( 'get' ) > -1 ) { + currentLevel.methods.push( 'head' ); + } // At leaf nodes also flag (at the top level) what arguments are // available to GET requests, so that we may automatically apply the diff --git a/tests/unit/lib/comments.js b/tests/unit/lib/comments.js index f55edf8d..85bb1e22 100644 --- a/tests/unit/lib/comments.js +++ b/tests/unit/lib/comments.js @@ -2,26 +2,24 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CommentsRequest = require( '../../../lib/comments' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); -describe.only( 'wp.comments', function() { +describe( 'wp.comments', function() { var site; var comments; beforeEach(function() { - site = WP.site( '/wp-json' ); + site = new WP({ + endpoint: '/wp-json', + username: 'foouser', + password: 'barpass' + }); comments = site.comments(); - // comments = new CommentsRequest(); }); describe( 'constructor', function() { - it( 'should create a CollectionRequest instance', function() { - expect( comments instanceof CollectionRequest ).to.be.true; - }); - it( 'should set any passed-in options', function() { comments = site.comments({ booleanProp: true, @@ -31,14 +29,23 @@ describe.only( 'wp.comments', function() { expect( comments._options.strProp ).to.equal( 'Some string' ); }); - it.skip( 'should default _options to {}', function() { - expect( comments._options ).to.deep.equal( {} ); + it( 'should initialize _options to the site defaults', function() { + expect( comments._options ).to.deep.equal({ + endpoint: '/wp-json/', + username: 'foouser', + password: 'barpass' + }); }); it( 'should initialize the base path component', function() { expect( comments._path ).to.deep.equal( { '0': 'comments' } ); }); + it( 'should set a default _supportedMethods array', function() { + expect( comments ).to.have.property( '_supportedMethods' ); + expect( comments._supportedMethods ).to.be.an( 'array' ); + }); + it( 'should inherit CommentsRequest from CollectionRequest', function() { expect( comments instanceof CollectionRequest ).to.be.true; expect( comments instanceof WPRequest ).to.be.true; @@ -57,17 +64,6 @@ describe.only( 'wp.comments', function() { }); - describe( '_pathValidators', function() { - - it( 'defines validators for id and action', function() { - var comments = new CommentsRequest(); - expect( comments._pathValidators ).to.deep.equal({ - id: /^\d+$/ - }); - }); - - }); - describe( 'query methods', function() { it( 'provides a method to set the ID', function() { @@ -82,15 +78,10 @@ describe.only( 'wp.comments', function() { expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/8' ); }); - it( 'converts ID parameters into integers', function() { - comments.id( 4.019 ); - expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/4' ); - }); - it( 'should update the supported methods when setting ID', function() { comments.id( 8 ); var _supportedMethods = comments._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); }); }); @@ -107,23 +98,39 @@ describe.only( 'wp.comments', function() { expect( path ).to.equal( '/wp-json/wp/v2/comments/1337' ); }); - it( 'throws an error if an invalid ID is specified', function() { + it( 'does not throw an error if a valid numeric ID is specified', function() { expect(function numberPassesValidation() { - comments._path = { id: 8 }; - comments._renderPath(); + comments.id( 8 ); + comments.validatePath(); }).not.to.throw(); + }); + + it( 'does not throw an error if a valid numeric ID is specified as a string', function() { + expect( function numberAsStringPassesValidation() { + comments.id( '8' ); + comments.validatePath(); + }).not.to.throw(); + }); + + it( 'throws an error if a non-integer numeric string ID is specified', function() { + expect( function nonIntegerNumberAsStringFailsValidation() { + comments.id( 4.019 ); + comments.validatePath(); + }).to.throw(); + }); + it( 'throws an error if a non-numeric string ID is specified', function() { expect(function stringFailsValidation() { - comments._path = { id: 'wombat' }; - comments._renderPath(); + comments.id( 'wombat' ); + comments.validatePath(); }).to.throw(); }); - it( 'should restrict template changes to a single instance', function() { + it( 'should restrict path changes to a single instance', function() { comments.id( 2 ); - var newComments = site.comments(); - var path = newComments.id( 3 )._renderURI(); - expect( path ).to.equal( '/wp-json/wp/v2/comments/3' ); + var newComments = site.comments().id( 3 ); + expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/2' ); + expect( newComments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/3' ); }); }); From 507298f1b88c263a37535de831e586cfee02c303 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Mon, 13 Jun 2016 23:12:10 -0400 Subject: [PATCH 23/32] Update posts unit tests to reflect new world order --- tests/unit/lib/posts.js | 147 ++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 80 deletions(-) diff --git a/tests/unit/lib/posts.js b/tests/unit/lib/posts.js index cd431078..14baed7b 100644 --- a/tests/unit/lib/posts.js +++ b/tests/unit/lib/posts.js @@ -1,26 +1,27 @@ 'use strict'; var expect = require( 'chai' ).expect; -var PostsRequest = require( '../../../lib/posts' ); +var WP = require( '../../../wp' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.posts', function() { + var site; + var posts; - describe( 'constructor', function() { - - var posts; - - beforeEach(function() { - posts = new PostsRequest(); + beforeEach(function() { + site = new WP({ + endpoint: '/wp-json', + username: 'foouser', + password: 'barpass' }); + posts = site.posts(); + }); - it( 'should create a PostsRequest instance', function() { - expect( posts instanceof PostsRequest ).to.be.true; - }); + describe( 'constructor', function() { it( 'should set any passed-in options', function() { - posts = new PostsRequest({ + posts = site.posts({ booleanProp: true, strProp: 'Some string' }); @@ -28,15 +29,21 @@ describe( 'wp.posts', function() { expect( posts._options.strProp ).to.equal( 'Some string' ); }); - it( 'should default _options to {}', function() { - expect( posts._options ).to.deep.equal( {} ); + it( 'should initialize _options to the site defaults', function() { + expect( posts._options ).to.deep.equal({ + endpoint: '/wp-json/', + username: 'foouser', + password: 'barpass' + }); }); - it( 'should intitialize instance properties', function() { - expect( posts._path ).to.deep.equal( {} ); - expect( posts._template ).to.equal( 'posts(/:id)(/:action)(/:actionId)' ); - var _supportedMethods = posts._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'get|head|post' ); + it( 'should initialize the base path component', function() { + expect( posts._path ).to.deep.equal( { '0': 'posts' } ); + }); + + it( 'should set a default _supportedMethods array', function() { + expect( posts ).to.have.property( '_supportedMethods' ); + expect( posts._supportedMethods ).to.be.an( 'array' ); }); it( 'should inherit PostsRequest from CollectionRequest', function() { @@ -59,52 +66,27 @@ describe( 'wp.posts', function() { }); - describe( '_pathValidators', function() { - - it( 'defines validators for id and action', function() { - var posts = new PostsRequest(); - expect( posts._pathValidators ).to.deep.equal({ - id: /^\d+$/, - action: /(meta|revisions)/ - }); - }); - - }); - describe( 'query methods', function() { - var posts; - - beforeEach(function() { - posts = new PostsRequest(); - posts._options = { - endpoint: '/wp-json/' - }; - }); - it( 'provides a method to set the ID', function() { expect( posts ).to.have.property( 'id' ); expect( posts.id ).to.be.a( 'function' ); posts.id( 314159 ); - expect( posts._path ).to.have.property( 'id' ); - expect( posts._path.id ).to.equal( 314159 ); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/314159' ); }); - it( 'parses ID parameters into integers', function() { + it( 'accepts ID parameters as strings', function() { posts.id( '8' ); - expect( posts._path ).to.have.property( 'id' ); - expect( posts._path.id ).to.equal( 8 ); - posts.id( 4.019 ); - expect( posts._path.id ).to.equal( 4 ); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/8' ); }); it( 'should update the supported methods when setting ID', function() { posts.id( 8 ); var _supportedMethods = posts._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); }); - it( 'provides a method to get the meta values for a post', function() { + it.skip( 'provides a method to get the meta values for a post', function() { expect( posts ).to.have.property( 'meta' ); expect( posts.meta ).to.be.a( 'function' ); posts.id( 3 ).meta(); @@ -112,25 +94,25 @@ describe( 'wp.posts', function() { expect( posts._path.action ).to.equal( 'meta' ); }); - it( 'should force authentication when querying posts/id/meta', function() { + it.skip( 'should force authentication when querying posts/id/meta', function() { posts.id( 1337 ).meta(); expect( posts._options ).to.have.property( 'auth' ); expect( posts._options.auth ).to.be.true; }); - it( 'should update the supported methods when querying for meta', function() { + it.skip( 'should update the supported methods when querying for meta', function() { posts.id( 1066 ).meta(); var _supportedMethods = posts._supportedMethods.sort().join( '|' ); expect( _supportedMethods ).to.equal( 'get|head|post' ); }); - it( 'provides a method to get specific post meta objects by ID', function() { + it.skip( 'provides a method to get specific post meta objects by ID', function() { posts.id( 3 ).meta( 5 ); expect( posts._path ).to.have.property( 'actionId' ); expect( posts._path.actionId ).to.equal( 5 ); }); - it( 'parses meta ID parameters into integers', function() { + it.skip( 'parses meta ID parameters into integers', function() { posts.id( 3 ).meta( '4' ); expect( posts._path ).to.have.property( 'actionId' ); expect( posts._path.actionId ).to.equal( 4 ); @@ -138,13 +120,13 @@ describe( 'wp.posts', function() { expect( posts._path.actionId ).to.equal( 3 ); }); - it( 'should force authentication when querying posts/id/meta/:id', function() { + it.skip( 'should force authentication when querying posts/id/meta/:id', function() { posts.id( 7331 ).meta( 7 ); expect( posts._options ).to.have.property( 'auth' ); expect( posts._options.auth ).to.be.true; }); - it( 'should update the supported methods when querying for meta', function() { + it.skip( 'should update the supported methods when querying for meta', function() { posts.id( 1066 ).meta( 2501 ); var _supportedMethods = posts._supportedMethods.sort().join( '|' ); expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); @@ -154,15 +136,6 @@ describe( 'wp.posts', function() { describe( 'URL Generation', function() { - var posts; - - beforeEach(function() { - posts = new PostsRequest(); - posts._options = { - endpoint: '/wp-json/' - }; - }); - it( 'should create the URL for retrieving all posts', function() { var path = posts._renderURI(); expect( path ).to.equal( '/wp-json/wp/v2/posts' ); @@ -173,24 +146,40 @@ describe( 'wp.posts', function() { expect( path ).to.equal( '/wp-json/wp/v2/posts/1337' ); }); - it( 'throws an error if an invalid ID is specified', function() { + it( 'does not throw an error if a valid numeric ID is specified', function() { expect(function numberPassesValidation() { - posts._path = { id: 8 }; - posts._renderPath(); + posts.id( 8 ); + posts.validatePath(); + }).not.to.throw(); + }); + + it( 'does not throw an error if a valid numeric ID is specified as a string', function() { + expect( function numberAsStringPassesValidation() { + posts.id( '8' ); + posts.validatePath(); }).not.to.throw(); + }); + + it( 'throws an error if a non-integer numeric string ID is specified', function() { + expect( function nonIntegerNumberAsStringFailsValidation() { + posts.id( 4.019 ); + posts.validatePath(); + }).to.throw(); + }); + it( 'throws an error if a non-numeric string ID is specified', function() { expect(function stringFailsValidation() { - posts._path = { id: 'wombat' }; - posts._renderPath(); + posts.id( 'wombat' ); + posts.validatePath(); }).to.throw(); }); - it( 'should create the URL for retrieving all meta for a specific post', function() { + it.skip( 'should create the URL for retrieving all meta for a specific post', function() { var path = posts.id( 1337 ).meta()._renderURI(); expect( path ).to.equal( '/wp-json/wp/v2/posts/1337/meta' ); }); - it( 'should create the URL for retrieving a specific meta item', function() { + it.skip( 'should create the URL for retrieving a specific meta item', function() { var path = posts.id( 1337 ).meta( 2001 )._renderURI(); expect( path ).to.equal( '/wp-json/wp/v2/posts/1337/meta/2001' ); }); @@ -200,18 +189,16 @@ describe( 'wp.posts', function() { expect( path ).to.equal( '/wp-json/wp/v2/posts/1337/revisions' ); }); - it( 'should force authentication when querying posts/id/revisions', function() { - posts.id( 1337 ).revisions(); - expect( posts._options ).to.have.property( 'auth' ); - expect( posts._options.auth ).to.be.true; + it( 'should create the URL for retrieving a specific revision item', function() { + var path = posts.id( 1337 ).revisions( 2001 )._renderURI(); + expect( path ).to.equal( '/wp-json/wp/v2/posts/1337/revisions/2001' ); }); - it( 'should restrict template changes to a single instance', function() { - posts._template = 'path/with/post/nr/:id'; - var newPosts = new PostsRequest(); - newPosts._options.endpoint = 'endpoint/url/'; - var path = newPosts.id( 3 )._renderURI(); - expect( path ).to.equal( 'endpoint/url/wp/v2/posts/3' ); + it( 'should restrict path changes to a single instance', function() { + posts.id( 2 ); + var newPosts = site.posts().id( 3 ).revisions(); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/2' ); + expect( newPosts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/3/revisions' ); }); }); From 5533e3e7072b36185556543b8e5db129dddecbe5 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 07:45:22 -0400 Subject: [PATCH 24/32] Update media unit tests and port structure changes to posts and comments --- tests/unit/lib/comments.js | 41 ++++++++------ tests/unit/lib/media.js | 96 +++++++++++++++---------------- tests/unit/lib/posts.js | 112 +++++++++++++++++++------------------ 3 files changed, 127 insertions(+), 122 deletions(-) diff --git a/tests/unit/lib/comments.js b/tests/unit/lib/comments.js index 85bb1e22..2d9b484b 100644 --- a/tests/unit/lib/comments.js +++ b/tests/unit/lib/comments.js @@ -5,7 +5,7 @@ var WP = require( '../../../wp' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); -describe( 'wp.comments', function() { +describe.only( 'wp.comments', function() { var site; var comments; @@ -38,7 +38,7 @@ describe( 'wp.comments', function() { }); it( 'should initialize the base path component', function() { - expect( comments._path ).to.deep.equal( { '0': 'comments' } ); + expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments' ); }); it( 'should set a default _supportedMethods array', function() { @@ -64,24 +64,31 @@ describe( 'wp.comments', function() { }); - describe( 'query methods', function() { + describe( 'path part setters', function() { - it( 'provides a method to set the ID', function() { - expect( comments ).to.have.property( 'id' ); - expect( comments.id ).to.be.a( 'function' ); - comments.id( 314159 ); - expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/314159' ); - }); + describe( '.id()', function() { - it( 'accepts ID parameters as strings', function() { - comments.id( '8' ); - expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/8' ); - }); + it( 'provides a method to set the ID', function() { + expect( comments ).to.have.property( 'id' ); + expect( comments.id ).to.be.a( 'function' ); + }); + + it( 'should set the ID value in the path', function() { + comments.id( 314159 ); + expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/314159' ); + }); + + it( 'accepts ID parameters as strings', function() { + comments.id( '8' ); + expect( comments._renderURI() ).to.equal( '/wp-json/wp/v2/comments/8' ); + }); + + it( 'should update the supported methods when setting ID', function() { + comments.id( 8 ); + var _supportedMethods = comments._supportedMethods.sort().join( '|' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); + }); - it( 'should update the supported methods when setting ID', function() { - comments.id( 8 ); - var _supportedMethods = comments._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); }); }); diff --git a/tests/unit/lib/media.js b/tests/unit/lib/media.js index 22a3f635..29cc5301 100644 --- a/tests/unit/lib/media.js +++ b/tests/unit/lib/media.js @@ -1,30 +1,27 @@ 'use strict'; var expect = require( 'chai' ).expect; -var MediaRequest = require( '../../../lib/media' ); +var WP = require( '../../../wp' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.media', function() { - + var site; var media; beforeEach(function() { - media = new MediaRequest(); + site = new WP({ + endpoint: '/wp-json', + username: 'foouser', + password: 'barpass' + }); + media = site.media(); }); describe( 'constructor', function() { - it( 'should create a MediaRequest instance', function() { - expect( media instanceof MediaRequest ).to.be.true; - }); - - it( 'should default _options to {}', function() { - expect( media._options ).to.deep.equal( {} ); - }); - it( 'should set any passed-in options', function() { - media = new MediaRequest({ + media = site.media({ booleanProp: true, strProp: 'Some string' }); @@ -32,27 +29,26 @@ describe( 'wp.media', function() { expect( media._options.strProp ).to.equal( 'Some string' ); }); - it( 'should intitialize instance properties', function() { - expect( media._path ).to.deep.equal( {} ); - expect( media._params ).to.deep.equal( {} ); - expect( media._template ).to.equal( 'media(/:id)' ); - var _supportedMethods = media._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'get|head|post' ); + it( 'should initialize _options to the site defaults', function() { + expect( media._options ).to.deep.equal({ + endpoint: '/wp-json/', + username: 'foouser', + password: 'barpass' + }); }); - it( 'should inherit MediaRequest from CollectionRequest', function() { - expect( media instanceof CollectionRequest ).to.be.true; - expect( media instanceof WPRequest ).to.be.true; + it( 'should initialize the base path component', function() { + expect( media._renderURI() ).to.equal( '/wp-json/wp/v2/media' ); }); - }); - - describe( '_pathValidators', function() { + it( 'should set a default _supportedMethods array', function() { + expect( media ).to.have.property( '_supportedMethods' ); + expect( media._supportedMethods ).to.be.an( 'array' ); + }); - it( 'has a validator for the "id" property', function() { - expect( media._pathValidators ).to.deep.equal({ - id: /^\d+$/ - }); + it( 'should inherit MediaRequest from CollectionRequest', function() { + expect( media instanceof CollectionRequest ).to.be.true; + expect( media instanceof WPRequest ).to.be.true; }); }); @@ -64,34 +60,38 @@ describe( 'wp.media', function() { expect( media.id ).to.be.a( 'function' ); }); - it( 'should set the ID value in the template', function() { + it( 'should set the ID value in the path', function() { media.id( 8 ); - expect( media._path ).to.have.property( 'id' ); - expect( media._path.id ).to.equal( 8 ); + expect( media._renderURI() ).to.equal( '/wp-json/wp/v2/media/8' ); }); it( 'should update the supported methods', function() { media.id( 8 ); var _supportedMethods = media._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); }); - it( 'replaces values on successive calls', function() { - media.id( 8 ).id( 3 ); - expect( media._path.id ).to.equal( 3 ); + it( 'throws an error on successive calls', function() { + expect(function successiveCallsThrowsError() { + media.id( 8 ).id( 3 ); + }).to.throw(); }); - it( 'causes a validation error when called with a non-number', function() { + it( 'passes validation when called with a number', function() { expect(function numberPassesValidation() { - media._path = { id: 8 }; - media._renderPath(); - media._path.id = '9'; - media._renderPath(); + media.id( 8 )._renderPath(); }).not.to.throw(); + }); + it( 'passes validation when called with a number formatted as a string', function() { + expect(function numberAsStringPassesValidation() { + media.id( '9' )._renderPath(); + }).not.to.throw(); + }); + + it( 'causes a validation error when called with a non-number', function() { expect(function stringFailsValidation() { - media._path = { id: 'wombat' }; - media._renderPath(); + media.id( 'wombat' )._renderPath(); }).to.throw(); }); @@ -99,25 +99,19 @@ describe( 'wp.media', function() { describe( 'url generation', function() { - beforeEach(function() { - media._options = { - endpoint: 'http://some-site.com/wp-json/' - }; - }); - it( 'should create the URL for the media collection', function() { var uri = media._renderURI(); - expect( uri ).to.equal( 'http://some-site.com/wp-json/wp/v2/media' ); + expect( uri ).to.equal( '/wp-json/wp/v2/media' ); }); it( 'can paginate the media collection responses', function() { var uri = media.page( 4 )._renderURI(); - expect( uri ).to.equal( 'http://some-site.com/wp-json/wp/v2/media?page=4' ); + expect( uri ).to.equal( '/wp-json/wp/v2/media?page=4' ); }); it( 'should create the URL for a specific media object', function() { var uri = media.id( 1492 )._renderURI(); - expect( uri ).to.equal( 'http://some-site.com/wp-json/wp/v2/media/1492' ); + expect( uri ).to.equal( '/wp-json/wp/v2/media/1492' ); }); }); diff --git a/tests/unit/lib/posts.js b/tests/unit/lib/posts.js index 14baed7b..3316fdbc 100644 --- a/tests/unit/lib/posts.js +++ b/tests/unit/lib/posts.js @@ -38,7 +38,7 @@ describe( 'wp.posts', function() { }); it( 'should initialize the base path component', function() { - expect( posts._path ).to.deep.equal( { '0': 'posts' } ); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts' ); }); it( 'should set a default _supportedMethods array', function() { @@ -66,70 +66,74 @@ describe( 'wp.posts', function() { }); - describe( 'query methods', function() { + describe( 'path part setters', function() { - it( 'provides a method to set the ID', function() { - expect( posts ).to.have.property( 'id' ); - expect( posts.id ).to.be.a( 'function' ); - posts.id( 314159 ); - expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/314159' ); - }); + describe( '.id()', function() { - it( 'accepts ID parameters as strings', function() { - posts.id( '8' ); - expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/8' ); - }); + it( 'provides a method to set the ID', function() { + expect( posts ).to.have.property( 'id' ); + expect( posts.id ).to.be.a( 'function' ); + }); - it( 'should update the supported methods when setting ID', function() { - posts.id( 8 ); - var _supportedMethods = posts._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); - }); + it( 'should set the ID value in the path', function() { + posts.id( 314159 ); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/314159' ); + }); - it.skip( 'provides a method to get the meta values for a post', function() { - expect( posts ).to.have.property( 'meta' ); - expect( posts.meta ).to.be.a( 'function' ); - posts.id( 3 ).meta(); - expect( posts._path ).to.have.property( 'action' ); - expect( posts._path.action ).to.equal( 'meta' ); - }); + it( 'accepts ID parameters as strings', function() { + posts.id( '8' ); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/8' ); + }); - it.skip( 'should force authentication when querying posts/id/meta', function() { - posts.id( 1337 ).meta(); - expect( posts._options ).to.have.property( 'auth' ); - expect( posts._options.auth ).to.be.true; - }); + it( 'should update the supported methods when setting ID', function() { + posts.id( 8 ); + var _supportedMethods = posts._supportedMethods.sort().join( '|' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); + }); - it.skip( 'should update the supported methods when querying for meta', function() { - posts.id( 1066 ).meta(); - var _supportedMethods = posts._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'get|head|post' ); }); - it.skip( 'provides a method to get specific post meta objects by ID', function() { - posts.id( 3 ).meta( 5 ); - expect( posts._path ).to.have.property( 'actionId' ); - expect( posts._path.actionId ).to.equal( 5 ); - }); + describe.skip( '.meta()', function() { - it.skip( 'parses meta ID parameters into integers', function() { - posts.id( 3 ).meta( '4' ); - expect( posts._path ).to.have.property( 'actionId' ); - expect( posts._path.actionId ).to.equal( 4 ); - posts.id( 3 ).meta( 3.14159 ); - expect( posts._path.actionId ).to.equal( 3 ); - }); + it( 'is defined', function() { + expect( posts ).to.have.property( 'meta' ); + expect( posts.meta ).to.be.a( 'function' ); + }); - it.skip( 'should force authentication when querying posts/id/meta/:id', function() { - posts.id( 7331 ).meta( 7 ); - expect( posts._options ).to.have.property( 'auth' ); - expect( posts._options.auth ).to.be.true; - }); + it( 'provides a method to get the meta values for a post', function() { + posts.id( 3 ).meta(); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/3/meta' ); + }); + + it( 'should force authentication when querying posts/id/meta', function() { + posts.id( 1337 ).meta(); + expect( posts._options ).to.have.property( 'auth' ); + expect( posts._options.auth ).to.be.true; + }); + + it( 'should update the supported methods when querying for meta', function() { + posts.id( 1066 ).meta(); + var _supportedMethods = posts._supportedMethods.sort().join( '|' ); + expect( _supportedMethods ).to.equal( 'get|head|post' ); + }); + + it( 'provides a method to get specific post meta objects by ID', function() { + posts.id( 3 ).meta( 5 ); + expect( posts._renderURI() ).to.equal( '/wp-json/wp/v2/posts/3/meta/5' ); + }); + + it( 'should force authentication when querying posts/id/meta/:id', function() { + posts.id( 7331 ).meta( 7 ); + expect( posts._options ).to.have.property( 'auth' ); + expect( posts._options.auth ).to.be.true; + }); + + it( 'should update the supported methods when querying for meta', function() { + posts.id( 1066 ).meta( 2501 ); + var _supportedMethods = posts._supportedMethods.sort().join( '|' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); + }); - it.skip( 'should update the supported methods when querying for meta', function() { - posts.id( 1066 ).meta( 2501 ); - var _supportedMethods = posts._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); }); }); From 79c26bb56dd8ed3987330b76a7b73c7b598ff811 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 08:00:22 -0400 Subject: [PATCH 25/32] Update pages unit tests to account for derived endpoint handlers --- tests/unit/lib/pages.js | 143 +++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/tests/unit/lib/pages.js b/tests/unit/lib/pages.js index 625137dd..901273a5 100644 --- a/tests/unit/lib/pages.js +++ b/tests/unit/lib/pages.js @@ -1,26 +1,27 @@ 'use strict'; var expect = require( 'chai' ).expect; -var PagesRequest = require( '../../../lib/pages' ); +var WP = require( '../../../wp' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.pages', function() { - - describe( 'constructor', function() { - - var pages; - - beforeEach(function() { - pages = new PagesRequest(); + var site; + var pages; + + beforeEach(function() { + site = new WP({ + endpoint: '/wp-json', + username: 'foouser', + password: 'barpass' }); + pages = site.pages(); + }); - it( 'should create a PagesRequest instance', function() { - expect( pages instanceof PagesRequest ).to.be.true; - }); + describe( 'constructor', function() { it( 'should set any passed-in options', function() { - pages = new PagesRequest({ + pages = site.pages({ booleanProp: true, strProp: 'Some string' }); @@ -28,16 +29,21 @@ describe( 'wp.pages', function() { expect( pages._options.strProp ).to.equal( 'Some string' ); }); - it( 'should default _options to {}', function() { - expect( pages._options ).to.deep.equal( {} ); + it( 'should initialize _options to the site defaults', function() { + expect( pages._options ).to.deep.equal({ + endpoint: '/wp-json/', + username: 'foouser', + password: 'barpass' + }); + }); + + it( 'should initialize the base path component', function() { + expect( pages._renderURI() ).to.equal( '/wp-json/wp/v2/pages' ); }); - it( 'should intitialize instance properties', function() { - expect( pages._path ).to.deep.equal( {} ); - expect( pages._params ).to.deep.equal( {} ); - expect( pages._template ).to.equal( 'pages(/:id)(/:action)(/:commentId)' ); - var _supportedMethods = pages._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'get|head|post' ); + it( 'should set a default _supportedMethods array', function() { + expect( pages ).to.have.property( '_supportedMethods' ); + expect( pages._supportedMethods ).to.be.an( 'array' ); }); it( 'should inherit PagesRequest from CollectionRequest', function() { @@ -60,42 +66,19 @@ describe( 'wp.pages', function() { }); - describe( '_pathValidators', function() { - - it( 'defines validators for action and commentId', function() { - var pages = new PagesRequest(); - expect( pages._pathValidators ).to.deep.equal({ - action: /(comments|revisions)/, - commentId: /^\d+$/ - }); - }); - - }); - describe( 'URL Generation', function() { - var pages; - - beforeEach(function() { - pages = new PagesRequest(); - pages._options = { - endpoint: '/wp-json/' - }; - }); - - it( 'should restrict template changes to a single instance', function() { - pages._template = 'path/with/post/nr/:id'; - var newPages = new PagesRequest(); - newPages._options.endpoint = 'endpoint/url/'; - var path = newPages.id( 3 )._renderURI(); - expect( path ).to.equal( 'endpoint/url/wp/v2/pages/3' ); + it( 'should restrict path changes to a single instance', function() { + pages.id( 2 ); + var newPages = site.pages().id( 3 ).revisions(); + expect( pages._renderURI() ).to.equal( '/wp-json/wp/v2/pages/2' ); + expect( newPages._renderURI() ).to.equal( '/wp-json/wp/v2/pages/3/revisions' ); }); describe( 'page collections', function() { it( 'should create the URL for retrieving all pages', function() { - var path = pages._renderURI(); - expect( path ).to.equal( '/wp-json/wp/v2/pages' ); + expect( pages._renderURI() ).to.equal( '/wp-json/wp/v2/pages' ); }); it( 'should provide filtering methods', function() { @@ -107,34 +90,53 @@ describe( 'wp.pages', function() { }); - describe( 'page resources', function() { + describe( '.id()', function() { + + it( 'should be defined', function() { + expect( pages ).to.have.property( 'id' ); + expect( pages.id ).to.be.a( 'function' ); + }); it( 'should create the URL for retrieving a specific post', function() { var path = pages.id( 1337 )._renderURI(); expect( path ).to.equal( '/wp-json/wp/v2/pages/1337' ); }); + it( 'should update the supported methods when setting ID', function() { + pages.id( 8 ); + var _supportedMethods = pages._supportedMethods.sort().join( '|' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); + }); + + }); + + describe( '.path()', function() { + + it( 'should be defined', function() { + expect( pages ).to.have.property( 'path' ); + expect( pages.path ).to.be.a( 'function' ); + }); + it( 'should create the URL for retrieving a post by path', function() { var path = pages.path( 'nested/page' )._renderURI(); expect( path ).to .equal( '/wp-json/wp/v2/pages?filter%5Bpagename%5D=nested%2Fpage' ); }); - it( 'should update the supported methods when setting ID', function() { - pages.id( 8 ); - var _supportedMethods = pages._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); - }); - it( 'should not update the supported methods when setting Path', function() { pages.path( 'page/path' ); var _supportedMethods = pages._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'get|head|post' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); }); }); - describe( 'comments', function() { + describe.skip( 'comments', function() { + + it( 'should be defined', function() { + expect( pages ).to.have.property( 'comments' ); + expect( pages.comments ).to.be.a( 'function' ); + }); it( 'should create the URL for a page\'s comments collection', function() { var path = pages.id( 1337 ).comments()._renderURI(); @@ -165,15 +167,24 @@ describe( 'wp.pages', function() { }); - it( 'should create the URL for retrieving the revisions for a specific post', function() { - var path = pages.id( 1337 ).revisions()._renderURI(); - expect( path ).to.equal( '/wp-json/wp/v2/pages/1337/revisions' ); - }); + describe( '.revisions()', function() { + + it( 'should be defined', function() { + expect( pages ).to.have.property( 'revisions' ); + expect( pages.revisions ).to.be.a( 'function' ); + }); + + it( 'should create the URL for retrieving the revisions for a specific post', function() { + var path = pages.id( 1337 ).revisions()._renderURI(); + expect( path ).to.equal( '/wp-json/wp/v2/pages/1337/revisions' ); + }); + + it.skip( 'should force authentication when querying pages/id/revisions', function() { + pages.id( 1337 ).revisions(); + expect( pages._options ).to.have.property( 'auth' ); + expect( pages._options.auth ).to.be.true; + }); - it( 'should force authentication when querying pages/id/revisions', function() { - pages.id( 1337 ).revisions(); - expect( pages._options ).to.have.property( 'auth' ); - expect( pages._options.auth ).to.be.true; }); }); From e7ef50c7bbeb3e50a6d85d2328df72544399cda6 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 08:08:25 -0400 Subject: [PATCH 26/32] Update tests for taxonomies endpoint --- tests/unit/lib/taxonomies.js | 84 ++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/unit/lib/taxonomies.js b/tests/unit/lib/taxonomies.js index 5e836e07..1abb7765 100644 --- a/tests/unit/lib/taxonomies.js +++ b/tests/unit/lib/taxonomies.js @@ -1,26 +1,27 @@ 'use strict'; var expect = require( 'chai' ).expect; -var TaxonomiesRequest = require( '../../../lib/taxonomies' ); +var WP = require( '../../../wp' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.taxonomies', function() { - - describe( 'constructor', function() { - - var taxonomies; - - beforeEach(function() { - taxonomies = new TaxonomiesRequest(); + var site; + var taxonomies; + + beforeEach(function() { + site = new WP({ + endpoint: '/wp-json', + username: 'foouser', + password: 'barpass' }); + taxonomies = site.taxonomies(); + }); - it( 'should create a TaxonomiesRequest instance', function() { - expect( taxonomies instanceof TaxonomiesRequest ).to.be.true; - }); + describe( 'constructor', function() { it( 'should set any passed-in options', function() { - taxonomies = new TaxonomiesRequest({ + taxonomies = site.taxonomies({ booleanProp: true, strProp: 'Some string' }); @@ -28,19 +29,24 @@ describe( 'wp.taxonomies', function() { expect( taxonomies._options.strProp ).to.equal( 'Some string' ); }); - it( 'should default _options to {}', function() { - expect( taxonomies._options ).to.deep.equal( {} ); + it( 'should initialize _options to the site defaults', function() { + expect( taxonomies._options ).to.deep.equal({ + endpoint: '/wp-json/', + username: 'foouser', + password: 'barpass' + }); }); - it( 'should intitialize instance properties', function() { - var _supportedMethods = taxonomies._supportedMethods.sort().join( '|' ); - expect( taxonomies._path ).to.deep.equal({ collection: 'taxonomies' }); - expect( taxonomies._params ).to.deep.equal( {} ); - expect( taxonomies._template ).to.equal( '(:collection)(/:term)' ); - expect( _supportedMethods ).to.equal( 'get|head' ); + it( 'should initialize the base path component', function() { + expect( taxonomies._renderURI() ).to.equal( '/wp-json/wp/v2/taxonomies' ); }); - it( 'should inherit PostsRequest from CollectionRequest', function() { + it( 'should set a default _supportedMethods array', function() { + expect( taxonomies ).to.have.property( '_supportedMethods' ); + expect( taxonomies._supportedMethods ).to.be.an( 'array' ); + }); + + it( 'should inherit TaxonomiesRequest from CollectionRequest', function() { expect( taxonomies instanceof CollectionRequest ).to.be.true; expect( taxonomies instanceof WPRequest ).to.be.true; }); @@ -58,35 +64,29 @@ describe( 'wp.taxonomies', function() { }); - describe( 'URL Generation', function() { + describe( 'path part setters', function() { - var taxonomies; + describe( '.taxonomy()', function() { - beforeEach(function() { - taxonomies = new TaxonomiesRequest(); - taxonomies._options = { - endpoint: '/wp-json/' - }; - }); + it( 'provides a method to set the taxonomy', function() { + expect( taxonomies ).to.have.property( 'taxonomy' ); + expect( taxonomies.taxonomy ).to.be.a( 'function' ); + }); - it( 'should create the URL for retrieving a specific collection', function() { - var url = taxonomies.collection( 'taxonomies' )._renderURI(); - expect( url ).to.equal( '/wp-json/wp/v2/taxonomies' ); }); - it( 'should create the URL for retrieving a specific taxonomy', function() { - var url = taxonomies.collection( 'taxonomies' ).term( 'my-tax' )._renderURI(); - expect( url ).to.equal( '/wp-json/wp/v2/taxonomies/my-tax' ); - }); + }); + + describe( 'URL Generation', function() { - it( 'should create the URL for retrieving taxonomies with a shared parent', function() { - var url = taxonomies.collection( 'categories' ).parent( 42 )._renderURI(); - expect( url ).to.equal( '/wp-json/wp/v2/categories?parent=42' ); + it( 'should create the URL for retrieving all taxonomies', function() { + var url = taxonomies._renderURI(); + expect( url ).to.equal( '/wp-json/wp/v2/taxonomies' ); }); - it( 'should permit specifying the parent for a collection of terms', function() { - var url = taxonomies.collection( 'categories' ).forPost( 1234 )._renderURI(); - expect( url ).to.equal( '/wp-json/wp/v2/categories?post=1234' ); + it( 'should create the URL for retrieving a specific taxonomy', function() { + var url = taxonomies.taxonomy( 'category' )._renderURI(); + expect( url ).to.equal( '/wp-json/wp/v2/taxonomies/category' ); }); }); From a0da2b4045aa6e653599f6625ebd33e62c21f7c0 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 08:10:37 -0400 Subject: [PATCH 27/32] Update types unit tests to handle derived endpoint handlers --- tests/unit/lib/types.js | 55 +++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/tests/unit/lib/types.js b/tests/unit/lib/types.js index e717ed6d..efb69028 100644 --- a/tests/unit/lib/types.js +++ b/tests/unit/lib/types.js @@ -1,26 +1,27 @@ 'use strict'; var expect = require( 'chai' ).expect; -var TypesRequest = require( '../../../lib/types' ); +var WP = require( '../../../wp' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.types', function() { - - describe( 'constructor', function() { - - var types; - - beforeEach(function() { - types = new TypesRequest(); + var site; + var types; + + beforeEach(function() { + site = new WP({ + endpoint: '/wp-json', + username: 'foouser', + password: 'barpass' }); + types = site.types(); + }); - it( 'should create a TypesRequest instance', function() { - expect( types instanceof TypesRequest ).to.be.true; - }); + describe( 'constructor', function() { it( 'should set any passed-in options', function() { - types = new TypesRequest({ + types = site.types({ booleanProp: true, strProp: 'Some string' }); @@ -28,16 +29,21 @@ describe( 'wp.types', function() { expect( types._options.strProp ).to.equal( 'Some string' ); }); - it( 'should default _options to {}', function() { - expect( types._options ).to.deep.equal( {} ); + it( 'should initialize _options to the site defaults', function() { + expect( types._options ).to.deep.equal({ + endpoint: '/wp-json/', + username: 'foouser', + password: 'barpass' + }); + }); + + it( 'should initialize the base path component', function() { + expect( types._renderURI() ).to.equal( '/wp-json/wp/v2/types' ); }); - it( 'should intitialize instance properties', function() { - var _supportedMethods = types._supportedMethods.sort().join( '|' ); - expect( types._path ).to.deep.equal( {} ); - expect( types._params ).to.deep.equal( {} ); - expect( types._template ).to.equal( 'types(/:type)' ); - expect( _supportedMethods ).to.equal( 'get|head' ); + it( 'should set a default _supportedMethods array', function() { + expect( types ).to.have.property( '_supportedMethods' ); + expect( types._supportedMethods ).to.be.an( 'array' ); }); it( 'should inherit PostsRequest from CollectionRequest', function() { @@ -62,15 +68,6 @@ describe( 'wp.types', function() { describe( 'URL Generation', function() { - var types; - - beforeEach(function() { - types = new TypesRequest(); - types._options = { - endpoint: '/wp-json/' - }; - }); - it( 'should create the URL for retrieving all types', function() { var url = types._renderURI(); expect( url ).to.equal( '/wp-json/wp/v2/types' ); From e6f5206d4d2513101b5a42536028b01aa42ecbae Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 08:16:48 -0400 Subject: [PATCH 28/32] Update users tests to account for new endpoint handlers --- tests/unit/lib/users.js | 94 ++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 57 deletions(-) diff --git a/tests/unit/lib/users.js b/tests/unit/lib/users.js index c7337e07..b2571fa7 100644 --- a/tests/unit/lib/users.js +++ b/tests/unit/lib/users.js @@ -1,24 +1,27 @@ 'use strict'; var expect = require( 'chai' ).expect; -var UsersRequest = require( '../../../lib/users' ); +var WP = require( '../../../wp' ); +var CollectionRequest = require( '../../../lib/shared/collection-request' ); +var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.users', function() { - + var site; var users; - describe( 'constructor', function() { - - beforeEach(function() { - users = new UsersRequest(); + beforeEach(function() { + site = new WP({ + endpoint: '/wp-json', + username: 'foouser', + password: 'barpass' }); + users = site.users(); + }); - it( 'should create a UsersRequest instance', function() { - expect( users instanceof UsersRequest ).to.be.true; - }); + describe( 'constructor', function() { it( 'should set any passed-in options', function() { - users = new UsersRequest({ + users = site.users({ booleanProp: true, strProp: 'Some string' }); @@ -26,22 +29,26 @@ describe( 'wp.users', function() { expect( users._options.strProp ).to.equal( 'Some string' ); }); - it( 'should force authentication', function() { - expect( users._options ).to.have.property( 'auth' ); - expect( users._options.auth ).to.be.true; - }); - - it( 'should default _options to { auth: true }', function() { + it( 'should initialize _options to the site defaults', function() { expect( users._options ).to.deep.equal({ - auth: true + endpoint: '/wp-json/', + username: 'foouser', + password: 'barpass' }); }); - it( 'should initialize instance properties', function() { - expect( users._path ).to.deep.equal( {} ); - expect( users._params ).to.deep.equal( {} ); - var _supportedMethods = users._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'get|head|post' ); + it( 'should initialize the base path component', function() { + expect( users._renderURI() ).to.equal( '/wp-json/wp/v2/users' ); + }); + + it( 'should set a default _supportedMethods array', function() { + expect( users ).to.have.property( '_supportedMethods' ); + expect( users._supportedMethods ).to.be.an( 'array' ); + }); + + it( 'should inherit UsersRequest from CollectionRequest', function() { + expect( users instanceof CollectionRequest ).to.be.true; + expect( users instanceof WPRequest ).to.be.true; }); it( 'should inherit prototype methods from both ancestors', function() { @@ -57,56 +64,31 @@ describe( 'wp.users', function() { }); - describe( '_pathValidators', function() { - - it( 'has a validator for the "id" property', function() { - var users = new UsersRequest(); - expect( users._pathValidators ).to.deep.equal({ - id: /(^\d+$|^me$)/ - }); - }); - - }); - describe( '.me()', function() { it( 'sets the path to users/me', function() { - var users = new UsersRequest(); - users._options = { - endpoint: 'url/endpoint' - }; users.me(); - expect( users._path ).to.have.property( 'id' ); - expect( users._path.id ).to.equal( 'me' ); + expect( users._renderURI() ).to.equal( '/wp-json/wp/v2/users/me' ); }); }); describe( '.id()', function() { + it( 'should be defined', function() { + expect( users ).to.have.property( 'id' ); + expect( users.id ).to.be.a( 'function' ); + }); + it( 'sets the path ID to the passed-in value', function() { - var users = new UsersRequest(); - users._options = { - endpoint: 'url/endpoint' - }; users.id( 2501 ); - expect( users._path ).to.have.property( 'id' ); - expect( users._path.id ).to.equal( 2501 ); + expect( users._renderURI() ).to.equal( '/wp-json/wp/v2/users/2501' ); }); }); describe( 'prototype._renderURI', function() { - var users; - - beforeEach(function() { - users = new UsersRequest(); - users._options = { - endpoint: '/wp-json/' - }; - }); - it( 'should create the URL for retrieving all users', function() { var url = users._renderURI(); expect( url ).to.equal( '/wp-json/wp/v2/users' ); @@ -114,16 +96,14 @@ describe( 'wp.users', function() { it( 'should create the URL for retrieving the current user', function() { var url = users.me()._renderURI(); - var _supportedMethods = users._supportedMethods.sort().join( '|' ); expect( url ).to.equal( '/wp-json/wp/v2/users/me' ); - expect( _supportedMethods ).to.equal( 'get|head' ); }); it( 'should create the URL for retrieving a specific user by ID', function() { var url = users.id( 1337 )._renderURI(); var _supportedMethods = users._supportedMethods.sort().join( '|' ); expect( url ).to.equal( '/wp-json/wp/v2/users/1337' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); + expect( _supportedMethods ).to.equal( 'delete|get|head|patch|post|put' ); }); }); From 5db59e7143ef27a649ebb747b3dfc66452c433ce Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 08:37:11 -0400 Subject: [PATCH 29/32] Update remaining tests to reflect derived endpoints --- lib/shared/wp-request.js | 4 ++ tests/integration/categories.js | 4 +- tests/integration/tags.js | 4 +- tests/integration/taxonomies.js | 43 ++------------- tests/unit/lib/comments.js | 2 +- tests/unit/lib/shared/wp-request.js | 15 ++---- tests/unit/lib/util/route-tree.js | 2 +- tests/unit/wp.js | 82 ++++++++++------------------- 8 files changed, 45 insertions(+), 111 deletions(-) diff --git a/lib/shared/wp-request.js b/lib/shared/wp-request.js index fbc25071..07fdfd60 100644 --- a/lib/shared/wp-request.js +++ b/lib/shared/wp-request.js @@ -427,6 +427,10 @@ WPRequest.prototype.validatePath = function() { for ( var level = 0; level <= maxLevel; level++ ) { + if ( ! this._levels || ! this._levels[ level ] ) { + continue; + } + if ( this._path[ level ] ) { // Validate the provided path level against all available path validators validatePathLevel( this._levels[ level ], this._path[ level ] ); diff --git a/tests/integration/categories.js b/tests/integration/categories.js index cafb95a2..92d5f99e 100644 --- a/tests/integration/categories.js +++ b/tests/integration/categories.js @@ -173,7 +173,7 @@ describe( 'integration: categories()', function() { }); - describe( 'term()', function() { + describe( 'id()', function() { it( 'can be used to access an individual category term', function() { var selectedCategory; @@ -181,7 +181,7 @@ describe( 'integration: categories()', function() { // Pick one of the categories selectedCategory = categories[ 3 ]; // Query for that category directly - return wp.categories().term( selectedCategory.id ); + return wp.categories().id( selectedCategory.id ); }).then(function( category ) { expect( category ).to.be.an( 'object' ); expect( category ).to.have.property( 'id' ); diff --git a/tests/integration/tags.js b/tests/integration/tags.js index b3c89415..28333542 100644 --- a/tests/integration/tags.js +++ b/tests/integration/tags.js @@ -177,7 +177,7 @@ describe( 'integration: tags()', function() { }); - describe( 'term()', function() { + describe( 'id()', function() { it( 'can be used to access an individual tag term', function() { var selectedTag; @@ -185,7 +185,7 @@ describe( 'integration: tags()', function() { // Pick one of the tags selectedTag = tags[ 3 ]; // Query for that tag directly - return wp.tags().term( selectedTag.id ); + return wp.tags().id( selectedTag.id ); }).then(function( tag ) { expect( tag ).to.be.an( 'object' ); expect( tag ).to.have.property( 'id' ); diff --git a/tests/integration/taxonomies.js b/tests/integration/taxonomies.js index 67247405..69811d29 100644 --- a/tests/integration/taxonomies.js +++ b/tests/integration/taxonomies.js @@ -30,8 +30,8 @@ describe( 'integration: taxonomies()', function() { return expect( prom ).to.eventually.equal( SUCCESS ); }); - it( 'can be chained with a term() call to fetch the category taxonomy', function() { - var prom = wp.taxonomies().term( 'category' ).get().then(function( category ) { + it( 'can be chained with a taxonomy() call to fetch the category taxonomy', function() { + var prom = wp.taxonomies().taxonomy( 'category' ).get().then(function( category ) { expect( category ).to.be.an( 'object' ); expect( category ).to.have.property( 'slug' ); expect( category.slug ).to.equal( 'category' ); @@ -42,43 +42,8 @@ describe( 'integration: taxonomies()', function() { return expect( prom ).to.eventually.equal( SUCCESS ); }); - it( 'can be chained with a term() call to fetch the post_tag taxonomy', function() { - var prom = wp.taxonomies().term( 'post_tag' ).get().then(function( tag ) { - expect( tag ).to.be.an( 'object' ); - expect( tag ).to.have.property( 'slug' ); - expect( tag.slug ).to.equal( 'post_tag' ); - expect( tag ).to.have.property( 'hierarchical' ); - expect( tag.hierarchical ).to.equal( false ); - return SUCCESS; - }); - return expect( prom ).to.eventually.equal( SUCCESS ); - }); - -}); - -describe( 'integration: taxonomy()', function() { - var wp; - - beforeEach(function() { - wp = new WP({ - endpoint: 'http://wpapi.loc/wp-json' - }); - }); - - it( 'can be used to directly retrieve the category taxonomy object', function() { - var prom = wp.taxonomy( 'category' ).get().then(function( category ) { - expect( category ).to.be.an( 'object' ); - expect( category ).to.have.property( 'slug' ); - expect( category.slug ).to.equal( 'category' ); - expect( category ).to.have.property( 'hierarchical' ); - expect( category.hierarchical ).to.equal( true ); - return SUCCESS; - }); - return expect( prom ).to.eventually.equal( SUCCESS ); - }); - - it( 'can be used to directly retrieve the post_tag taxonomy object', function() { - var prom = wp.taxonomy( 'post_tag' ).get().then(function( tag ) { + it( 'can be chained with a taxonomy() call to fetch the post_tag taxonomy', function() { + var prom = wp.taxonomies().taxonomy( 'post_tag' ).get().then(function( tag ) { expect( tag ).to.be.an( 'object' ); expect( tag ).to.have.property( 'slug' ); expect( tag.slug ).to.equal( 'post_tag' ); diff --git a/tests/unit/lib/comments.js b/tests/unit/lib/comments.js index 2d9b484b..e59ec4cb 100644 --- a/tests/unit/lib/comments.js +++ b/tests/unit/lib/comments.js @@ -5,7 +5,7 @@ var WP = require( '../../../wp' ); var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); -describe.only( 'wp.comments', function() { +describe( 'wp.comments', function() { var site; var comments; diff --git a/tests/unit/lib/shared/wp-request.js b/tests/unit/lib/shared/wp-request.js index 8dc23578..a6394817 100644 --- a/tests/unit/lib/shared/wp-request.js +++ b/tests/unit/lib/shared/wp-request.js @@ -65,28 +65,19 @@ describe( 'WPRequest', function() { expect( request._renderPath() ).to.equal( 'ns' ); }); - it( 'prefixes any provided template', function() { - request._template = 'face'; - request.namespace( 'nose' ); - expect( request._renderPath() ).to.equal( 'nose/face' ); - }); - it( 'can accept & set a namespace in the (:domain/:version) format', function() { - request._template = 'template'; request.namespace( 'ns/v3' ); - expect( request._renderPath() ).to.equal( 'ns/v3/template' ); + expect( request._renderPath() ).to.equal( 'ns/v3' ); }); it( 'can be removed (to use the legacy api v1) with an empty string', function() { - request._template = 'template'; request.namespace( 'windows/xp' ).namespace( '' ); - expect( request._renderPath() ).to.equal( 'template' ); + expect( request._renderPath() ).to.equal( '' ); }); it( 'can be removed (to use the legacy api v1) by omitting arguments', function() { - request._template = 'template'; request.namespace( 'wordpress/95' ).namespace(); - expect( request._renderPath() ).to.equal( 'template' ); + expect( request._renderPath() ).to.equal( '' ); }); }); diff --git a/tests/unit/lib/util/route-tree.js b/tests/unit/lib/util/route-tree.js index e50cfdb6..24ff95b8 100644 --- a/tests/unit/lib/util/route-tree.js +++ b/tests/unit/lib/util/route-tree.js @@ -83,7 +83,7 @@ describe( 'route-tree utility', function() { expect( routeTree ).to.have.property( 'level' ); expect( routeTree.level ).to.equal( 0 ); expect( routeTree ).to.have.property( 'methods' ); - expect( routeTree.methods ).to.deep.equal([ 'get', 'post' ]); + expect( routeTree.methods.sort().join( '|' ) ).to.equal( 'get|head|post' ); expect( routeTree ).to.have.property( 'namedGroup' ); expect( routeTree.namedGroup ).to.equal( false ); expect( routeTree ).to.have.property( 'names' ); diff --git a/tests/unit/wp.js b/tests/unit/wp.js index 5662cd6d..91dd2e4f 100644 --- a/tests/unit/wp.js +++ b/tests/unit/wp.js @@ -3,13 +3,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../' ); -// Other constructors, for use with instanceof checks -var MediaRequest = require( '../../lib/media' ); -var PagesRequest = require( '../../lib/pages' ); -var PostsRequest = require( '../../lib/posts' ); -var TaxonomiesRequest = require( '../../lib/taxonomies' ); -var TypesRequest = require( '../../lib/types' ); -var UsersRequest = require( '../../lib/users' ); +// Constructors, for use with instanceof checks var CollectionRequest = require( '../../lib/shared/collection-request' ); var WPRequest = require( '../../lib/shared/wp-request' ); @@ -97,33 +91,27 @@ describe( 'wp', function() { describe( '.root()', function() { + beforeEach(function() { + site = new WP({ endpoint: 'http://my.site.com/wp-json' }); + }); + it( 'is defined', function() { expect( site ).to.have.property( 'root' ); expect( site.root ).to.be.a( 'function' ); }); it( 'creates a get request against the root endpoint', function() { - site._options.endpoint = 'http://my.site.com/wp-json/'; var request = site.root(); expect( request._renderURI() ).to.equal( 'http://my.site.com/wp-json/' ); }); - it( 'takes a "path" property to query a root-relative path', function() { - site._options.endpoint = 'http://my.site.com/wp-json/'; + it( 'takes a "path" argument to query a root-relative path', function() { var request = site.root( 'custom/endpoint' ); expect( request._renderURI() ).to.equal( 'http://my.site.com/wp-json/custom/endpoint' ); }); - it( 'creates a basic WPRequest if "collection" is unspecified or "false"', function() { - var pathRequest = site.root( 'some/relative/root' ); - expect( pathRequest._template ).to.equal( 'some/relative/root' ); - expect( pathRequest instanceof WPRequest ).to.be.true; - expect( pathRequest instanceof CollectionRequest ).to.be.false; - }); - - it( 'creates a CollectionRequest object if "collection" is "true"', function() { - var pathRequest = site.root( 'some/collection/endpoint', true ); - expect( pathRequest._template ).to.equal( 'some/collection/endpoint' ); + it( 'creates a CollectionRequest object', function() { + var pathRequest = site.root( 'some/collection/endpoint' ); expect( pathRequest instanceof WPRequest ).to.be.true; expect( pathRequest instanceof CollectionRequest ).to.be.true; }); @@ -145,57 +133,43 @@ describe( 'wp', function() { describe( 'endpoint accessors', function() { it( 'defines a media endpoint handler', function() { - var media = site.media(); - expect( media instanceof MediaRequest ).to.be.true; + expect( site ).to.have.property( 'media' ); + expect( site.media ).to.be.a( 'function' ); }); it( 'defines a pages endpoint handler', function() { - var posts = site.pages(); - expect( posts instanceof PagesRequest ).to.be.true; + expect( site ).to.have.property( 'pages' ); + expect( site.pages ).to.be.a( 'function' ); }); it( 'defines a posts endpoint handler', function() { - var posts = site.posts(); - expect( posts instanceof PostsRequest ).to.be.true; + expect( site ).to.have.property( 'posts' ); + expect( site.posts ).to.be.a( 'function' ); }); it( 'defines a taxonomies endpoint handler', function() { - var posts = site.taxonomies(); - expect( posts instanceof TaxonomiesRequest ).to.be.true; - }); - - it( 'defines a types endpoint handler', function() { - var posts = site.types(); - expect( posts instanceof TypesRequest ).to.be.true; + expect( site ).to.have.property( 'taxonomies' ); + expect( site.taxonomies ).to.be.a( 'function' ); }); - it( 'defines a users endpoint handler', function() { - var posts = site.users(); - expect( posts instanceof UsersRequest ).to.be.true; + it( 'defines a categories endpoint handler', function() { + expect( site ).to.have.property( 'categories' ); + expect( site.categories ).to.be.a( 'function' ); }); - }); - - describe( 'taxonomy shortcut handlers', function() { - - it( 'defines a .categories() shortcut for the category terms collection', function() { - var categories = site.categories(); - expect( categories instanceof TaxonomiesRequest ).to.be.true; - expect( categories._renderURI() ).to - .equal( 'endpoint/url/wp/v2/categories' ); + it( 'defines a tags endpoint handler', function() { + expect( site ).to.have.property( 'tags' ); + expect( site.tags ).to.be.a( 'function' ); }); - it( 'defines a .tags() shortcut for the tag terms collection', function() { - var tags = site.tags(); - expect( tags instanceof TaxonomiesRequest ).to.be.true; - expect( tags._renderURI() ).to.equal( 'endpoint/url/wp/v2/tags' ); + it( 'defines a types endpoint handler', function() { + expect( site ).to.have.property( 'types' ); + expect( site.types ).to.be.a( 'function' ); }); - it( 'defines a generic .taxonomy() handler for arbitrary taxonomy objects', function() { - var taxRequest = site.taxonomy( 'my_custom_tax' ); - expect( taxRequest instanceof TaxonomiesRequest ).to.be.true; - var uri = taxRequest._renderURI(); - expect( uri ).to.equal( 'endpoint/url/wp/v2/taxonomies/my_custom_tax' ); + it( 'defines a users endpoint handler', function() { + expect( site ).to.have.property( 'users' ); + expect( site.users ).to.be.a( 'function' ); }); }); From 3fa8e48d7a786aa714a8e6ef179066fc9b481f2f Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 08:56:37 -0400 Subject: [PATCH 30/32] Deprecate CollectionRequest and remove unused constructors CollectionRequest tests that have not been superceded by the mixin test suite have been moved to WPRequest --- lib/comments.js | 156 ------ lib/media.js | 106 ---- lib/pages.js | 175 ------ lib/posts.js | 153 ------ lib/shared/collection-request.js | 121 +---- lib/taxonomies.js | 128 ----- lib/types.js | 90 ---- lib/users.js | 122 ----- tests/unit/lib/mixins/filters.js | 14 + tests/unit/lib/mixins/parameters.js | 25 +- tests/unit/lib/shared/collection-request.js | 565 -------------------- tests/unit/lib/shared/wp-request.js | 128 ++++- tests/unit/lib/types.js | 13 - 13 files changed, 165 insertions(+), 1631 deletions(-) delete mode 100644 lib/comments.js delete mode 100644 lib/media.js delete mode 100644 lib/pages.js delete mode 100644 lib/posts.js delete mode 100644 lib/taxonomies.js delete mode 100644 lib/types.js delete mode 100644 lib/users.js delete mode 100644 tests/unit/lib/shared/collection-request.js diff --git a/lib/comments.js b/lib/comments.js deleted file mode 100644 index 5ecb9c38..00000000 --- a/lib/comments.js +++ /dev/null @@ -1,156 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule CommentsRequest - * @beta - */ -var CollectionRequest = require( './shared/collection-request' ); -var pick = require( 'lodash' ).pick; -var extend = require( 'node.extend' ); -var inherit = require( 'util' ).inherits; - -var parameters = require( './mixins/parameters' ); - -/** - * CommentsRequest extends CollectionRequest to handle the /comments API endpoint - * - * @class CommentsRequest - * @constructor - * @extends CollectionRequest - * @param {Object} options A hash of options for the CommentsRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function CommentsRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of non-filter query parameters - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = {}; - - /** - * The URL template that will be used to assemble endpoint paths - * - * @property _template - * @type String - * @protected - * @default 'comments(/:id)' - */ - this._template = 'comments(/:id)'; - - /** - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get', 'post' ] - */ - this._supportedMethods = [ 'head', 'get', 'post' ]; - - // Default all .comments() requests to assume a query against the WP API v2 endpoints - this.namespace( 'wp/v2' ); -} - -// CommentsRequest extends CollectionRequest -inherit( CommentsRequest, CollectionRequest ); - -// Mixins -extend( CommentsRequest.prototype, pick( parameters, [ - 'parent', - 'forPost' -] ) ); - -/** - * A hash table of path keys and regex validators for those path elements - * - * @property _pathValidators - * @type Object - * @private - */ -CommentsRequest.prototype._pathValidators = { - - /** - * ID must be an integer - * - * @property _pathValidators.id - * @type {RegExp} - */ - id: /^\d+$/ -}; - -/** - * Specify a post ID to query - * - * @method id - * @chainable - * @param {Number} id The ID of a post to retrieve - * @return {CommentsRequest} The CommentsRequest instance (for chaining) - */ -CommentsRequest.prototype.id = function( id ) { - this._path.id = parseInt( id, 10 ); - this._supportedMethods = [ 'head', 'get', 'put', 'post', 'delete' ]; - - return this; -}; - -/** - * Specify the name of the taxonomy collection to query - * - * The collections will not be a strict match to defined comments: *e.g.*, to - * get the list of terms for the taxonomy "category," you must specify the - * collection name "categories" (similarly, specify "tags" to get a list of terms - * for the "post_tag" taxonomy). - * - * To get the dictionary of all available comments, specify the collection - * "taxonomy" (slight misnomer: this case will return an object, not the array - * that would usually be expected with a "collection" request). - * - * @method collection - * @chainable - * @param {String} taxonomyCollection The name of the taxonomy collection to query - * @return {CommentsRequest} The CommentsRequest instance (for chaining) - */ -CommentsRequest.prototype.collection = function( taxonomyCollection ) { - this._path.collection = taxonomyCollection; - - return this; -}; - -/** - * Specify a taxonomy term to request - * - * @method term - * @chainable - * @param {String} term The ID or slug of the term to request - * @return {CommentsRequest} The CommentsRequest instance (for chaining) - */ -CommentsRequest.prototype.term = function( term ) { - this._path.term = term; - - return this; -}; - -module.exports = CommentsRequest; diff --git a/lib/media.js b/lib/media.js deleted file mode 100644 index 6df9a575..00000000 --- a/lib/media.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule MediaRequest - * @beta - */ -var CollectionRequest = require( './shared/collection-request' ); -var inherit = require( 'util' ).inherits; - -/** - * MediaRequest extends CollectionRequest to handle the /media API endpoint - * - * @class MediaRequest - * @constructor - * @extends CollectionRequest - * @param {Object} options A hash of options for the MediaRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function MediaRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of non-filter query parameters - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = {}; - - /** - * The URL template that will be used to assemble endpoint paths - * - * @property _template - * @type String - * @protected - * @default 'media(/:id)' - */ - this._template = 'media(/:id)'; - - /** - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get', 'post' ] - */ - this._supportedMethods = [ 'head', 'get', 'post' ]; - - // Default all .media() requests to assume a query against the WP API v2 endpoints - this.namespace( 'wp/v2' ); -} - -inherit( MediaRequest, CollectionRequest ); - -/** - * A hash table of path keys and regex validators for those path elements - * - * @property _pathValidators - * @type Object - * @private - */ -MediaRequest.prototype._pathValidators = { - - /** - * ID must be an integer or "me" - * - * @property _pathValidators.id - * @type {RegExp} - */ - id: /^\d+$/ -}; - -/** - * @method id - * @chainable - * @param {Number} id The integer ID of a media record - * @return {MediaRequest} The MediaRequest instance (for chaining) - */ -MediaRequest.prototype.id = function( id ) { - this._path.id = parseInt( id, 10 ); - this._supportedMethods = [ 'head', 'get', 'put', 'post', 'delete' ]; - - return this; -}; - -module.exports = MediaRequest; diff --git a/lib/pages.js b/lib/pages.js deleted file mode 100644 index f870d19d..00000000 --- a/lib/pages.js +++ /dev/null @@ -1,175 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule PagesRequest - * @beta - */ -var CollectionRequest = require( './shared/collection-request' ); -var pick = require( 'lodash' ).pick; -var extend = require( 'node.extend' ); -var inherit = require( 'util' ).inherits; - -var filters = require( './mixins/filters' ); - -/** - * PagesRequest extends CollectionRequest to handle the /posts API endpoint - * - * @class PagesRequest - * @constructor - * @extends CollectionRequest - * @param {Object} options A hash of options for the PagesRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function PagesRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of non-filter query parameters - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = {}; - - /** - * The URL template that will be used to assemble endpoint paths - * - * @property _template - * @type String - * @private - * @default 'pages(/:id)(/:action)(/:commentId)' - */ - this._template = 'pages(/:id)(/:action)(/:commentId)'; - - /** - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get', 'post' ] - */ - this._supportedMethods = [ 'head', 'get', 'post' ]; - - // Default all .pages() requests to assume a query against the WP API v2 endpoints - this.namespace( 'wp/v2' ); -} - -inherit( PagesRequest, CollectionRequest ); - -// Mixins -extend( PagesRequest.prototype, pick( filters, [ - // Specify that we are requesting a page by its path - 'path' -] ) ); - -/** - * A hash table of path keys and regex validators for those path elements - * - * @property _pathValidators - * @type Object - * @private - */ -PagesRequest.prototype._pathValidators = { - - // No validation on "id", since it can be a string path OR a numeric ID - - /** - * Action must be 'comments' or 'revisions' - * - * @property _pathValidators.action - * @type {RegExp} - * @private - */ - action: /(comments|revisions)/, - - /** - * Comment ID must be an integer - * - * @property _pathValidators.commentId - * @type {RegExp} - * @private - */ - commentId: /^\d+$/ -}; - -/** - * Specify a post ID to query - * - * @method id - * @chainable - * @return {PagesRequest} The PagesRequest instance (for chaining) - */ -PagesRequest.prototype.id = function( id ) { - this._path.id = parseInt( id, 10 ); - - this._supportedMethods = [ 'head', 'get', 'put', 'post', 'delete' ]; - - return this; -}; - -/** - * Specify that we are getting the comments for a specific page - * - * @method comments - * @chainable - * @return {PagesRequest} The PagesRequest instance (for chaining) - */ -PagesRequest.prototype.comments = function() { - this._path.action = 'comments'; - this._supportedMethods = [ 'head', 'get' ]; - - return this; -}; - -/** - * Specify a particular comment to retrieve - * (forces action "comments") - * - * @method comment - * @chainable - * @param {Number} id The ID of the comment to retrieve - * @return {PagesRequest} - */ -PagesRequest.prototype.comment = function( id ) { - this._path.action = 'comments'; - this._path.commentId = parseInt( id, 10 ); - this._supportedMethods = [ 'head', 'get', 'delete' ]; - - return this; -}; - -/** - * Specify that we are requesting the revisions for a specific post (forces basic auth) - * - * @method revisions - * @chainable - * @return {PagesRequest} The PagesRequest instance (for chaining) - */ -PagesRequest.prototype.revisions = function() { - this._path.action = 'revisions'; - this._supportedMethods = [ 'head', 'get' ]; - - return this.auth(); -}; - -module.exports = PagesRequest; diff --git a/lib/posts.js b/lib/posts.js deleted file mode 100644 index b0d75cb4..00000000 --- a/lib/posts.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule PostsRequest - * @beta - */ -var CollectionRequest = require( './shared/collection-request' ); -var inherit = require( 'util' ).inherits; - -/** - * PostsRequest extends CollectionRequest to handle the /posts API endpoint - * - * @class PostsRequest - * @constructor - * @extends CollectionRequest - * @param {Object} options A hash of options for the PostsRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function PostsRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of non-filter query parameters - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = {}; - - /** - * The URL template that will be used to assemble endpoint paths - * - * @property _template - * @type String - * @private - * @default 'posts(/:id)(/:action)(/:actionId)' - */ - this._template = 'posts(/:id)(/:action)(/:actionId)'; - - /** - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get', 'post' ] - */ - this._supportedMethods = [ 'head', 'get', 'post' ]; - - // Default all .posts() requests to assume a query against the WP API v2 endpoints - this.namespace( 'wp/v2' ); -} - -inherit( PostsRequest, CollectionRequest ); - -/** - * A hash table of path keys and regex validators for those path elements - * - * @property _pathValidators - * @type Object - * @private - */ -PostsRequest.prototype._pathValidators = { - - /** - * ID must be an integer - * - * @property _pathValidators.id - * @type {RegExp} - */ - id: /^\d+$/, - - /** - * Action must be one of 'meta' or 'revisions' - * - * @property _pathValidators.action - * @type {RegExp} - */ - action: /(meta|revisions)/ -}; - -/** - * Specify a post ID to query - * - * @method id - * @chainable - * @param {Number} id The ID of a post to retrieve - * @return {PostsRequest} The PostsRequest instance (for chaining) - */ -PostsRequest.prototype.id = function( id ) { - this._path.id = parseInt( id, 10 ); - this._supportedMethods = [ 'head', 'get', 'put', 'post', 'delete' ]; - - return this; -}; - -/** - * Specify that we are retrieving Post Meta (forces basic auth) - * - * Either return a collection of all meta objects for the specified post, - * or (if a meta ID was provided) return the requested meta object. - * - * @method meta - * @chainable - * @param {Number} [metaId] ID of a specific meta property to retrieve - * @return {PostsRequest} The PostsRequest instance (for chainin) - */ -PostsRequest.prototype.meta = function( metaId ) { - this._path.action = 'meta'; - this._supportedMethods = [ 'head', 'get', 'post' ]; - this._path.actionId = parseInt( metaId, 10 ) || null; - - if ( this._path.actionId ) { - this._supportedMethods = [ 'head', 'get', 'put', 'post', 'delete' ]; - } - - return this.auth(); -}; - -/** - * Specify that we are requesting the revisions for a specific post (forces basic auth) - * - * @method revisions - * @chainable - * @return {PostsRequest} The PostsRequest instance (for chaining) - */ -PostsRequest.prototype.revisions = function() { - this._path.action = 'revisions'; - this._supportedMethods = [ 'head', 'get' ]; - - return this.auth(); -}; - -module.exports = PostsRequest; diff --git a/lib/shared/collection-request.js b/lib/shared/collection-request.js index f839582f..29986fed 100644 --- a/lib/shared/collection-request.js +++ b/lib/shared/collection-request.js @@ -5,124 +5,5 @@ * @beta */ var WPRequest = require( './wp-request' ); -var pick = require( 'lodash' ).pick; -var extend = require( 'node.extend' ); -var inherit = require( 'util' ).inherits; -var filters = require( '../mixins/filters' ); -var parameters = require( '../mixins/parameters' ); - -/** - * CollectionRequest extends WPRequest with properties & methods for filtering collections - * via query parameters. It is the base constructor for most top-level WP instance methods. - * - * @class CollectionRequest - * @constructor - * @extends WPRequest - * @extensionfor PagesRequest - * @extensionfor PostsRequest - * @extensionfor TaxonomiesRequest - * @extensionfor TypesRequest - * @extensionfor UsersRequest - * @param {Object} options A hash of options for the CollectionRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function CollectionRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of filter values to parse into the final request URI - * @property _filters - * @type Object - * @private - * @default {} - */ - this._filters = {}; - - /** - * A hash of taxonomy terms to parse into the final request URI - * @property _taxonomyFilters - * @type Object - * @private - * @default {} - */ - this._taxonomyFilters = {}; - - /** - * A hash of non-filter query parameters - * This is used to store the query values for Type, Page & Context - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = {}; - - /** - * The URL template that will be used to assemble endpoint paths - * - * @property _template - * @type String - * @private - * @default '' - */ - this._template = ''; - - /** - * An array of supported methods; to be overridden by descendent constructors - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get', 'put', 'post', 'delete' ] - */ - this._supportedMethods = [ 'head', 'get', 'put', 'post', 'delete' ]; -} - -inherit( CollectionRequest, WPRequest ); - -// Mixins -extend( CollectionRequest.prototype, pick( filters, [ - // Dependency of all other filter parameters, as well as parameterMixins.author - 'filter', - // Taxomy handling - 'taxonomy', - 'category', - 'tag', - // Date filter handling - 'year', - 'month', - 'day' -] ) ); - -extend( CollectionRequest.prototype, pick( parameters, [ - // Pagination - 'page', - 'perPage', - // Other query parameters - 'slug', - 'name', - 'search', - 'author' -] ) ); - -module.exports = CollectionRequest; +module.exports = WPRequest; diff --git a/lib/taxonomies.js b/lib/taxonomies.js deleted file mode 100644 index 850d7d52..00000000 --- a/lib/taxonomies.js +++ /dev/null @@ -1,128 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule TaxonomiesRequest - * @beta - */ -var CollectionRequest = require( './shared/collection-request' ); -var extend = require( 'node.extend' ); -var inherit = require( 'util' ).inherits; -var parameters = require( './mixins/parameters' ); -var pick = require( 'lodash' ).pick; - -/** - * TaxonomiesRequest extends CollectionRequest to handle the /taxonomies API endpoint - * - * @class TaxonomiesRequest - * @constructor - * @extends CollectionRequest - * @param {Object} options A hash of options for the TaxonomiesRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function TaxonomiesRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of non-filter query parameters - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * Default to requesting the taxonomies "collection" (dictionary of publicly- - * registered taxonomies) if no other collection is specified - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = { collection: 'taxonomies' }; - - /** - * The URL template that will be used to assemble endpoint paths - * - * There is no path validation for taxonomies requests: terms can be numeric - * (categories) or strings (tags), and the list of registered collections is - * not fixed (it can be augmented or modified through plugin and theme behavior). - * - * @property _template - * @type String - * @private - * @default '(:collection)(/:term)' - */ - this._template = '(:collection)(/:term)'; - - /** - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get' ] - */ - this._supportedMethods = [ 'head', 'get' ]; - - // Default all .taxonomies() requests to assume a query against the WP API v2 endpoints - this.namespace( 'wp/v2' ); -} - -// TaxonomiesRequest extends CollectionRequest -inherit( TaxonomiesRequest, CollectionRequest ); - -/** - * Specify the name of the taxonomy collection to query - * - * The collections will not be a strict match to defined taxonomies: *e.g.*, to - * get the list of terms for the taxonomy "category," you must specify the - * collection name "categories" (similarly, specify "tags" to get a list of terms - * for the "post_tag" taxonomy). - * - * To get the dictionary of all available taxonomies, specify the collection - * "taxonomy" (slight misnomer: this case will return an object, not the array - * that would usually be expected with a "collection" request). - * - * @method collection - * @chainable - * @param {String} taxonomyCollection The name of the taxonomy collection to query - * @return {TaxonomiesRequest} The TaxonomiesRequest instance (for chaining) - */ -TaxonomiesRequest.prototype.collection = function( taxonomyCollection ) { - this._path.collection = taxonomyCollection; - - return this; -}; - -/** - * Specify a taxonomy term to request - * - * @method term - * @chainable - * @param {String} term The ID or slug of the term to request - * @return {TaxonomiesRequest} The TaxonomiesRequest instance (for chaining) - */ -TaxonomiesRequest.prototype.term = function( term ) { - this._path.term = term; - - return this; -}; - -extend( TaxonomiesRequest.prototype, pick( parameters, [ - 'parent', - 'forPost' -] ) ); - -module.exports = TaxonomiesRequest; diff --git a/lib/types.js b/lib/types.js deleted file mode 100644 index 0ac658ca..00000000 --- a/lib/types.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule TypesRequest - * @beta - */ -var CollectionRequest = require( './shared/collection-request' ); -var inherit = require( 'util' ).inherits; - -/** - * TypesRequest extends CollectionRequest to handle the /taxonomies API endpoint - * - * @class TypesRequest - * @constructor - * @extends CollectionRequest - * @param {Object} options A hash of options for the TypesRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function TypesRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of non-filter query parameters - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = {}; - - /** - * The URL template that will be used to assemble request URI paths - * - * @property _template - * @type String - * @private - * @default 'types(/:type)' - */ - this._template = 'types(/:type)'; - - /** - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get' ] - */ - this._supportedMethods = [ 'head', 'get' ]; - - // Default all .types() requests to assume a query against the WP API v2 endpoints - this.namespace( 'wp/v2' ); -} - -// TypesRequest extends CollectionRequest -inherit( TypesRequest, CollectionRequest ); - -/** - * Specify the name of the type to query - * - * @method type - * @chainable - * @param {String} typeName The name of the type to query - * @return {TypesRequest} The TypesRequest instance (for chaining) - */ -TypesRequest.prototype.type = function( typeName ) { - this._path.type = typeName; - - return this; -}; - -module.exports = TypesRequest; diff --git a/lib/users.js b/lib/users.js deleted file mode 100644 index f5116f6d..00000000 --- a/lib/users.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule UsersRequest - * @beta - */ -var CollectionRequest = require( './shared/collection-request' ); -var inherit = require( 'util' ).inherits; - -/** - * UsersRequest extends CollectionRequest to handle the `/users` API endpoint. The `/users` - * endpoint responds with a 401 error without authentication, so `users()` forces basic auth. - * - * @class UsersRequest - * @constructor - * @extends CollectionRequest - * @param {Object} options A hash of options for the UsersRequest instance - * @param {String} options.endpoint The endpoint URI for the invoking WP instance - * @param {String} [options.username] A username for authenticating API requests - * @param {String} [options.password] A password for authenticating API requests - */ -function UsersRequest( options ) { - /** - * Configuration options for the request such as the endpoint for the invoking WP instance - * @property _options - * @type Object - * @private - * @default {} - */ - this._options = options || {}; - - /** - * A hash of non-filter query parameters - * - * @property _params - * @type Object - * @private - * @default {} - */ - this._params = {}; - - /** - * A hash of values to assemble into the API request path - * - * @property _path - * @type Object - * @private - * @default {} - */ - this._path = {}; - - /** - * The URL template that will be used to assemble endpoint paths - * - * @property _template - * @type String - * @protected - * @default 'users(/:id)' - */ - this._template = 'users(/:id)'; - - /** - * @property _supportedMethods - * @type Array - * @private - * @default [ 'head', 'get', 'post' ] - */ - this._supportedMethods = [ 'head', 'get', 'post' ]; - - // Default all .users() requests to assume a query against the WP API v2 endpoints - this.namespace( 'wp/v2' ); - - // Force authentication on all users requests - return this.auth(); -} - -inherit( UsersRequest, CollectionRequest ); - -/** - * A hash table of path keys and regex validators for those path elements - * - * @property _pathValidators - * @type Object - * @private - */ -UsersRequest.prototype._pathValidators = { - - /** - * ID must be an integer or "me" - * - * @property _pathValidators.id - * @type {RegExp} - */ - id: /(^\d+$|^me$)/ -}; - -/** - * @method me - * @chainable - * @return {UsersRequest} The UsersRequest instance (for chaining) - */ -UsersRequest.prototype.me = function() { - this._path.id = 'me'; - this._supportedMethods = [ 'head', 'get' ]; - - return this; -}; - -/** - * @method id - * @chainable - * @param {Number} id The integer ID of a user record - * @return {UsersRequest} The UsersRequest instance (for chaining) - */ -UsersRequest.prototype.id = function( id ) { - this._path.id = parseInt( id, 10 ); - this._supportedMethods = [ 'head', 'get', 'put', 'post', 'delete' ]; - - return this; -}; - -module.exports = UsersRequest; diff --git a/tests/unit/lib/mixins/filters.js b/tests/unit/lib/mixins/filters.js index c5beb8c8..dd3bd7e7 100644 --- a/tests/unit/lib/mixins/filters.js +++ b/tests/unit/lib/mixins/filters.js @@ -208,6 +208,20 @@ describe( 'mixins: filter', function() { expect( getQueryStr( result ) ).to.equal( 'filter[tag]=cat' ); }); + it( 'de-dupes the taxonomy list when called with an array', function() { + req.taxonomy( 'post_tag', [ + 'disclosure', + 'alunageorge', + 'disclosure', + 'lorde', + 'lorde', + 'clean-bandit' + ]); + expect( req._taxonomyFilters ).to.deep.equal({ + tag: [ 'alunageorge', 'clean-bandit', 'disclosure', 'lorde' ] + }); + }); + it( 'supports setting an array of string terms', function() { // TODO: Multiple terms may be deprecated by API! var result = req.taxonomy( 'tag', [ 'a', 'b' ] ); diff --git a/tests/unit/lib/mixins/parameters.js b/tests/unit/lib/mixins/parameters.js index 49a822ec..a7d4ba95 100644 --- a/tests/unit/lib/mixins/parameters.js +++ b/tests/unit/lib/mixins/parameters.js @@ -57,6 +57,11 @@ describe( 'mixins: parameters', function() { expect( getQueryStr( result ) ).to.equal( 'page=7' ); }); + it( 'should be chainable and replace values when called multiple times', function() { + var result = req.page( 71 ).page( 2 ); + expect( getQueryStr( result ) ).to.equal( 'page=2' ); + }); + }); describe( '.perPage()', function() { @@ -87,6 +92,11 @@ describe( 'mixins: parameters', function() { expect( getQueryStr( result ) ).to.equal( 'per_page=7' ); }); + it( 'should be chainable and replace values when called multiple times', function() { + var result = req.perPage( 71 ).perPage( 2 ); + expect( getQueryStr( result ) ).to.equal( 'per_page=2' ); + }); + }); }); @@ -230,8 +240,19 @@ describe( 'mixins: parameters', function() { }); it( 'sets the "author_name" filter when provided a string value', function() { - var result = req.author( 'han-solo' ); - expect( getQueryStr( result ) ).to.equal( 'filter[author_name]=han-solo' ); + var result = req.author( 'jamesagarfield' ); + expect( getQueryStr( result ) ).to.equal( 'filter[author_name]=jamesagarfield' ); + }); + + it( 'is chainable, and replaces author_name values on subsequent calls', function() { + var result = req.author( 'fforde' ).author( 'bronte' ); + expect( result ).to.equal( req ); + expect( getQueryStr( result ) ).to.equal( 'filter[author_name]=bronte' ); + }); + + it( 'is chainable, and replaces author ID values on subsequent calls', function() { + var result = req.author( 1847 ); + expect( getQueryStr( result ) ).to.equal( 'author=1847' ); }); it( 'unsets author when called with an empty string', function() { diff --git a/tests/unit/lib/shared/collection-request.js b/tests/unit/lib/shared/collection-request.js deleted file mode 100644 index 2180ca1e..00000000 --- a/tests/unit/lib/shared/collection-request.js +++ /dev/null @@ -1,565 +0,0 @@ -'use strict'; -/*jshint -W106 */// Disable underscore_case warnings in this file b/c WP uses them -var chai = require( 'chai' ); -var expect = chai.expect; -var sinon = require( 'sinon' ); -chai.use( require( 'sinon-chai' ) ); - -var CollectionRequest = require( '../../../../lib/shared/collection-request' ); -var WPRequest = require( '../../../../lib/shared/wp-request' ); -var filterMixins = require( '../../../../lib/mixins/filters' ); - -describe( 'CollectionRequest', function() { - - var request; - - beforeEach(function() { - request = new CollectionRequest(); - request._options.endpoint = '/'; - }); - - describe( 'constructor', function() { - - it( 'should create a CollectionRequest instance', function() { - expect( request instanceof CollectionRequest ).to.be.true; - }); - - it( 'should inherit from WPRequest', function() { - expect( request instanceof WPRequest ).to.be.true; - }); - - it( 'should intitialize instance properties', function() { - var _supportedMethods = request._supportedMethods.sort().join( '|' ); - expect( _supportedMethods ).to.equal( 'delete|get|head|post|put' ); - expect( request._filters ).to.deep.equal( {} ); - expect( request._taxonomyFilters ).to.deep.equal( {} ); - expect( request._params ).to.deep.equal( {} ); - expect( request._template ).to.equal( '' ); - }); - - it( 'initializes requests with a _params dictionary', function() { - expect( request ).to.have.property( '_params' ); - expect( request._params ).to.deep.equal( {} ); - }); - - }); - - describe( 'parameter convenience methods', function() { - - describe( 'page', function() { - - it( 'should be defined', function() { - expect( request ).to.have.property( 'page' ); - expect( request.page ).to.be.a( 'function' ); - }); - - it( 'wraps .param()', function() { - sinon.stub( request, 'param' ); - request.page( 9 ); - expect( request.param ).to.have.been.calledWith( 'page', 9 ); - }); - - it( 'should set the "page" parameter', function() { - request.page( 2 ); - expect( request._params ).to.have.property( 'page' ); - expect( request._params.page ).to.equal( 2 ); - }); - - it( 'should map to the "page=N" query parameter', function() { - var path = request.page( 71 )._renderURI(); - expect( path ).to.equal( '/?page=71' ); - }); - - it( 'should replace values when called multiple times', function() { - var path = request.page( 71 ).page( 2 )._renderURI(); - expect( path ).to.equal( '/?page=2' ); - }); - - }); - - describe( 'perPage()', function() { - - it( 'function should exist', function() { - expect( request ).to.have.property( 'perPage' ); - expect( request.perPage ).to.be.a( 'function' ); - }); - - it( 'should set the "per_page=N" query parameter', function() { - var path = request.perPage( 6 )._renderURI(); - expect( path ).to.equal( '/?per_page=6' ); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.perPage( 71 ).perPage( 2 ) ).to.equal( request ); - var path = request.perPage( 71 ).perPage( 2 )._renderURI(); - expect( path ).to.equal( '/?per_page=2' ); - }); - - }); - - describe( 'context', function() { - - it( 'should be defined', function() { - expect( request ).to.have.property( 'context' ); - expect( request.context ).to.be.a( 'function' ); - }); - - it( 'wraps .param()', function() { - sinon.stub( request, 'param' ); - request.context( 'view' ); - expect( request.param ).to.have.been.calledWith( 'context', 'view' ); - }); - - it( 'should set the "context" parameter', function() { - request.context( 'edit' ); - expect( request._params ).to.have.property( 'context' ); - expect( request._params.context ).to.equal( 'edit' ); - }); - - it( 'should map to the "context=VALUE" query parameter', function() { - var path = request.context( 'edit' )._renderURI(); - expect( path ).to.equal( '/?context=edit' ); - }); - - it( 'should replace values when called multiple times', function() { - var path = request.context( 'edit' ).context( 'view' )._renderURI(); - expect( path ).to.equal( '/?context=view' ); - }); - - it( 'should provide a .edit() shortcut for .context( "edit" )', function() { - sinon.spy( request, 'context' ); - request.edit(); - expect( request.context ).to.have.been.calledWith( 'edit' ); - expect( request._renderURI() ).to.equal( '/?context=edit' ); - }); - - it( 'should force authentication when called with "edit"', function() { - request.edit(); - expect( request._options ).to.have.property( 'auth' ); - expect( request._options.auth ).to.be.true; - }); - - }); - - }); - - describe( 'embed()', function() { - - it( 'should be a function', function() { - expect( request ).to.have.property( 'embed' ); - expect( request.embed ).to.be.a( 'function' ); - }); - - it( 'should set the "_embed" parameter', function() { - request.embed(); - expect( request._params._embed ).to.equal( true ); - }); - - it( 'should be chainable', function() { - expect( request.embed() ).to.equal( request ); - }); - - }); - - describe( 'filter()', function() { - - it( 'should set the internal _filters hash', function() { - request.filter({ - someFilterProp: 'filter-value', - postsPerPage: 7 - }); - expect( request._filters ).to.deep.equal({ - someFilterProp: 'filter-value', - postsPerPage: 7 - }); - }); - - it( 'should support passing a single filter property as key & value arguments', function() { - request.filter( 'postType', 'page' ); - expect( request._filters ).to.deep.equal({ - postType: 'page' - }); - }); - - it( 'should support redefining filter values', function() { - request.filter( 'postStatus', 'draft' ); - request.filter( 'postStatus', 'publish' ); - expect( request._filters.postStatus ).to.equal( 'publish' ); - }); - - it( 'should support chaining filters', function() { - request.filter({ - someFilterProp: 'filter-value' - }).filter({ - postsPerPage: 7 - }).filter( 'postStatus', 'draft' ); - expect( request._filters ).to.deep.equal({ - someFilterProp: 'filter-value', - postsPerPage: 7, - postStatus: 'draft' - }); - }); - - }); - - describe( 'filtering convenience methods', function() { - - beforeEach(function() { - request._taxonomyFilters = {}; - request._filters = {}; - }); - - describe( 'taxonomy()', function() { - - it( 'should throw if an invalid term argument is provided', function() { - expect(function() { - request.taxonomy( 'tag', 'slug' ); - }).not.to.throw(); - - expect(function() { - request.taxonomy( 'cat', 7 ); - }).not.to.throw(); - - expect(function() { - request.taxonomy( 'category_name', [ 'slug1', 'slug2' ] ); - }).not.to.throw(); - - expect(function() { - request.taxonomy( 'tag', {} ); - }).to.throw(); - }); - - it( 'should store taxonomy terms in a sorted array, keyed by taxonomy', function() { - request.taxonomy( 'some_tax', 'nigel' ); - request.taxonomy( 'some_tax', [ 'tufnel', 'derek', 'smalls' ] ); - expect( request._taxonomyFilters ).to.deep.equal({ - some_tax: [ 'derek', 'nigel', 'smalls', 'tufnel' ] - }); - request - .taxonomy( 'drummers', [ 'stumpy', 'mama' ] ) - .taxonomy( 'drummers', [ 'stumpy-joe', 'james' ] ) - .taxonomy( 'drummers', 'ric' ); - expect( request._taxonomyFilters ).to.deep.equal({ - some_tax: [ 'derek', 'nigel', 'smalls', 'tufnel' ], - drummers: [ 'james', 'mama', 'ric', 'stumpy', 'stumpy-joe' ] - }); - }); - - it( 'should handle numeric terms, for category and taxonomy ID', function() { - request.taxonomy( 'age', [ 42, 2001, 13 ] ); - expect( request._taxonomyFilters ).to.deep.equal({ - age: [ 13, 42, 2001 ] - }); - }); - - it( 'should map "category" to "cat" for numeric terms', function() { - request.taxonomy( 'category', 7 ); - expect( request._taxonomyFilters ).to.deep.equal({ - cat: [ 7 ] - }); - request.taxonomy( 'category', [ 10, 2 ] ); - expect( request._taxonomyFilters ).to.deep.equal({ - cat: [ 2, 7, 10 ] - }); - }); - - it( 'should map "category" to "category_name" for string terms', function() { - request.taxonomy( 'category', 'news' ); - expect( request._taxonomyFilters ).to.deep.equal({ - category_name: [ 'news' ] - }); - request.taxonomy( 'category', [ 'events', 'fluxus-happenings' ] ); - expect( request._taxonomyFilters ).to.deep.equal({ - category_name: [ 'events', 'fluxus-happenings', 'news' ] - }); - }); - - it( 'should map "post_tag" to "tag" for tag terms', function() { - request.taxonomy( 'post_tag', 'disclosure' ); - expect( request._taxonomyFilters ).to.deep.equal({ - tag: [ 'disclosure' ] - }); - request.taxonomy( 'post_tag', [ 'white-noise', 'settle' ] ); - expect( request._taxonomyFilters ).to.deep.equal({ - tag: [ 'disclosure', 'settle', 'white-noise' ] - }); - }); - - it( 'de-dupes the taxonomy list', function() { - request.taxonomy( 'post_tag', [ - 'disclosure', - 'alunageorge', - 'disclosure', - 'lorde', - 'lorde', - 'clean-bandit' - ]); - expect( request._taxonomyFilters ).to.deep.equal({ - tag: [ 'alunageorge', 'clean-bandit', 'disclosure', 'lorde' ] - }); - }); - - }); - - describe( 'category()', function() { - - it( 'delegates to taxonomy() mixin', function() { - sinon.stub( filterMixins, 'taxonomy' ); - request.category( 'news' ); - expect( filterMixins.taxonomy ).to.have.been.calledWith( 'category', 'news' ); - filterMixins.taxonomy.restore(); - }); - - it( 'should be chainable, and accumulates values', function() { - expect( request.category( 'bat-country' ).category( 'bunny' ) ).to.equal( request ); - expect( request._taxonomyFilters ).to.deep.equal({ - category_name: [ 'bat-country', 'bunny' ] - }); - }); - - }); - - describe( 'tag()', function() { - - it( 'delegates to taxonomy() mixin', function() { - sinon.stub( filterMixins, 'taxonomy' ); - request.tag( 'the-good-life' ); - expect( filterMixins.taxonomy ).to.have.been.calledWith( 'tag', 'the-good-life' ); - filterMixins.taxonomy.restore(); - }); - - it( 'should be chainable, and accumulates values', function() { - expect( request.tag( 'drive-by' ).tag( 'jackson-pollock' ) ).to.equal( request ); - expect( request._taxonomyFilters ).to.deep.equal({ - tag: [ 'drive-by', 'jackson-pollock' ] - }); - }); - - }); - - describe( 'search()', function() { - - it( 'should do nothing if no search string is provided', function() { - request.search( '' ); - expect( request._renderQuery() ).to.equal( '' ); - }); - - it( 'should set the "s" filter property on the request object', function() { - request.search( 'Some search string' ); - expect( request._params.search ).to.equal( 'Some search string' ); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.search( 'str1' ).search( 'str2' ) ).to.equal( request ); - expect( request._params.search ).to.equal( 'str2' ); - }); - - }); - - describe( 'author()', function() { - - it( 'should set the "author" filter property for numeric arguments', function() { - request.author( 301 ); - expect( request._params.author ).to.equal( 301 ); - expect( request._filters.author_name ).not.to.exist; - }); - - it( 'should set the "author_name" filter property for string arguments', function() { - request.author( 'jamesagarfield' ); - expect( request._filters.author_name ).to.equal( 'jamesagarfield' ); - expect( request._params.author ).not.to.exist; - }); - - it( 'should throw an error if arguments are neither string nor number', function() { - expect(function() { - request.author({ some: 'object' }); - }).to.throw(); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.author( 'fforde' ).author( 'bronte' ) ).to.equal( request ); - expect( request._filters.author_name ).to.equal( 'bronte' ); - - request.author( 1847 ); - expect( request._filters.author_name ).not.to.exist; - expect( request._params.author ).to.equal( 1847 ); - }); - - }); - - describe( 'slug()', function() { - - it( 'should set the "slug" parameter on the request', function() { - request.slug( 'greatest-post-in-the-world' ); - expect( request._renderURI() ).to.equal( '/?slug=greatest-post-in-the-world' ); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.slug( 'post-slug-1' ).slug( 'hello-world' ) ).to.equal( request ); - expect( request._renderURI() ).to.equal( '/?slug=hello-world' ); - }); - - }); - - describe( 'name()', function() { - - it( 'should alias through to set the "slug" parameter on the request', function() { - request.name( 'greatest-post-in-the-world' ); - expect( request._renderURI() ).to.equal( '/?slug=greatest-post-in-the-world' ); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.name( 'post-slug-1' ).name( 'hello-world' ) ).to.equal( request ); - expect( request._renderURI() ).to.equal( '/?slug=hello-world' ); - }); - - }); - - describe( 'year()', function() { - - it( 'function should exist', function() { - expect( request.year ).to.exist; - expect( request.year ).to.be.a( 'function' ); - }); - - it( 'should set the "year" filter property on the request object', function() { - request.year( 2014 ); - expect( request._filters.year ).to.equal( 2014 ); - }); - - it( 'should accept year numbers as strings', function() { - request.year( '1066' ); - expect( request._filters.year ).to.equal( '1066' ); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.year( 1999 ).year( 2000 ) ).to.equal( request ); - expect( request._filters.year ).to.equal( 2000 ); - }); - - }); - - describe( 'month()', function() { - - it( 'function should exist', function() { - expect( request.month ).to.exist; - expect( request.month ).to.be.a( 'function' ); - }); - - it( 'should set the "monthnum" filter property on the request object', function() { - request.month( 7 ); - expect( request._filters.monthnum ).to.equal( 7 ); - }); - - it( 'should accept month numbers as strings', function() { - request.month( '3' ); - expect( request._filters.monthnum ).to.equal( 3 ); - }); - - it( 'should convert month name strings to month numbers', function() { - request.month( 'March' ); - expect( request._filters.monthnum ).to.equal( 3 ); - request.month( 'november' ); - expect( request._filters.monthnum ).to.equal( 11 ); - request.month( 'Jul' ); - expect( request._filters.monthnum ).to.equal( 7 ); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.month( 2 ).month( 'September' ) ).to.equal( request ); - expect( request._filters.monthnum ).to.equal( 9 ); - }); - - it( 'should not set anything if an invalid string is provided', function() { - request.month( 'The oldest in the family is moving with authority' ); - expect( request._filters.monthnum ).to.be.undefined; - }); - - it( 'should not set anything if a non-number is provided', function() { - request.month({ - wake: 'me up', - when: 'September ends' - }); - expect( request._filters.monthnum ).to.be.undefined; - }); - - }); - - describe( 'day()', function() { - - it( 'function should exist', function() { - expect( request.day ).to.exist; - expect( request.day ).to.be.a( 'function' ); - }); - - it( 'should set the "day" filter property on the request object', function() { - request.day( 7 ); - expect( request._filters.day ).to.equal( 7 ); - }); - - it( 'should accept day numbers as strings', function() { - request.day( '9' ); - expect( request._filters.day ).to.equal( '9' ); - }); - - it( 'should be chainable, and replace values', function() { - expect( request.day( 7 ).day( 22 ) ).to.equal( request ); - expect( request._filters.day ).to.equal( 22 ); - }); - - }); - - }); - - describe( '_renderQuery()', function() { - - it( 'properly parses taxonomy filters', function() { - request._taxonomyFilters = { - tag: [ 'clouds ', 'islands' ], - custom_tax: [ 7 ] - }; - var query = request._renderQuery(); - // Filters should be in alpha order, to support caching requests - expect( query ).to - .equal( '?filter%5Bcustom_tax%5D=7&filter%5Btag%5D=clouds%2Bislands' ); - }); - - it( 'lower-cases taxonomy terms', function() { - request._taxonomyFilters = { - tag: [ 'Diamond-Dust' ] - }; - var query = request._renderQuery(); - expect( query ).to.equal( '?filter%5Btag%5D=diamond-dust' ); - }); - - it( 'properly parses regular filters', function() { - request._filters = { - post_status: 'publish', s: 'Some search string' - }; - var query = request._renderQuery(); - expect( query ).to - .equal( '?filter%5Bpost_status%5D=publish&filter%5Bs%5D=Some%20search%20string' ); - }); - - it( 'properly parses array filters', function() { - request._filters = { post__in: [ 0, 1 ] }; - var query = request._renderQuery(); - expect( query ).to - .equal( '?filter%5Bpost__in%5D%5B%5D=0&filter%5Bpost__in%5D%5B%5D=1' ); - }); - - it( 'correctly merges taxonomy and regular filters & renders them in order', function() { - request._taxonomyFilters = { - cat: [ 7, 10 ] - }; - request._filters = { - name: 'some-slug' - }; - var query = request._renderQuery(); - // Filters should be in alpha order, to support caching requests - expect( query ).to.equal( '?filter%5Bcat%5D=7%2B10&filter%5Bname%5D=some-slug' ); - }); - - }); - -}); diff --git a/tests/unit/lib/shared/wp-request.js b/tests/unit/lib/shared/wp-request.js index a6394817..1c2ef25f 100644 --- a/tests/unit/lib/shared/wp-request.js +++ b/tests/unit/lib/shared/wp-request.js @@ -6,6 +6,7 @@ var sinon = require( 'sinon' ); var sandbox = require( 'sandboxed-module' ); var WPRequest = require( '../../../../lib/shared/wp-request' ); +var filterMixins = require( '../../../../lib/mixins/filters' ); describe( 'WPRequest', function() { @@ -37,6 +38,65 @@ describe( 'WPRequest', function() { }); + describe( '_renderQuery()', function() { + + beforeEach(function() { + Object.keys( filterMixins ).forEach(function( mixin ) { + if ( ! request[ mixin ] ) { + request[ mixin ] = filterMixins[ mixin ]; + } + }); + }); + + it( 'properly parses taxonomy filters', function() { + request._taxonomyFilters = { + tag: [ 'clouds ', 'islands' ], + custom_tax: [ 7 ] + }; + var query = request._renderQuery(); + // Filters should be in alpha order, to support caching requests + expect( query ).to + .equal( '?filter%5Bcustom_tax%5D=7&filter%5Btag%5D=clouds%2Bislands' ); + }); + + it( 'lower-cases taxonomy terms', function() { + request._taxonomyFilters = { + tag: [ 'Diamond-Dust' ] + }; + var query = request._renderQuery(); + expect( query ).to.equal( '?filter%5Btag%5D=diamond-dust' ); + }); + + it( 'properly parses regular filters', function() { + request._filters = { + post_status: 'publish', s: 'Some search string' + }; + var query = request._renderQuery(); + expect( query ).to + .equal( '?filter%5Bpost_status%5D=publish&filter%5Bs%5D=Some%20search%20string' ); + }); + + it( 'properly parses array filters', function() { + request._filters = { post__in: [ 0, 1 ] }; + var query = request._renderQuery(); + expect( query ).to + .equal( '?filter%5Bpost__in%5D%5B%5D=0&filter%5Bpost__in%5D%5B%5D=1' ); + }); + + it( 'correctly merges taxonomy and regular filters & renders them in order', function() { + request._taxonomyFilters = { + cat: [ 7, 10 ] + }; + request._filters = { + name: 'some-slug' + }; + var query = request._renderQuery(); + // Filters should be in alpha order, to support caching requests + expect( query ).to.equal( '?filter%5Bcat%5D=7%2B10&filter%5Bname%5D=some-slug' ); + }); + + }); + describe( '_checkMethodSupport', function() { it( 'should return true when called with a supported method', function() { @@ -51,7 +111,7 @@ describe( 'WPRequest', function() { }).to.throw(); }); - }); // constructor + }); describe( 'namespace', function() { @@ -155,6 +215,72 @@ describe( 'WPRequest', function() { }); + describe( 'parameter convenience methods', function() { + + describe( 'context', function() { + + beforeEach(function() { + request = new WPRequest({ + endpoint: '/' + }); + }); + + it( 'should be defined', function() { + expect( request ).to.have.property( 'context' ); + expect( request.context ).to.be.a( 'function' ); + }); + + it( 'wraps .param()', function() { + sinon.stub( request, 'param' ); + request.context( 'view' ); + expect( request.param ).to.have.been.calledWith( 'context', 'view' ); + }); + + it( 'should map to the "context=VALUE" query parameter', function() { + var path = request.context( 'edit' )._renderURI(); + expect( path ).to.equal( '/?context=edit' ); + }); + + it( 'should replace values when called multiple times', function() { + var path = request.context( 'edit' ).context( 'view' )._renderURI(); + expect( path ).to.equal( '/?context=view' ); + }); + + it( 'should provide a .edit() shortcut for .context( "edit" )', function() { + sinon.spy( request, 'context' ); + request.edit(); + expect( request.context ).to.have.been.calledWith( 'edit' ); + expect( request._renderURI() ).to.equal( '/?context=edit' ); + }); + + it( 'should force authentication when called with "edit"', function() { + request.edit(); + expect( request._options ).to.have.property( 'auth' ); + expect( request._options.auth ).to.be.true; + }); + + }); + + describe( 'embed()', function() { + + it( 'should be a function', function() { + expect( request ).to.have.property( 'embed' ); + expect( request.embed ).to.be.a( 'function' ); + }); + + it( 'should set the "_embed" parameter', function() { + request.embed(); + expect( request._params._embed ).to.equal( true ); + }); + + it( 'should be chainable', function() { + expect( request.embed() ).to.equal( request ); + }); + + }); + + }); + describe( 'auth', function() { it( 'is defined', function() { diff --git a/tests/unit/lib/types.js b/tests/unit/lib/types.js index efb69028..e7123d03 100644 --- a/tests/unit/lib/types.js +++ b/tests/unit/lib/types.js @@ -51,19 +51,6 @@ describe( 'wp.types', function() { expect( types instanceof WPRequest ).to.be.true; }); - it( 'should inherit prototype methods from both ancestors', function() { - // Spot-check from CollectionRequest: - expect( types ).to.have.property( 'filter' ); - expect( types.filter ).to.be.a( 'function' ); - expect( types ).to.have.property( 'param' ); - expect( types.param ).to.be.a( 'function' ); - // From WPRequest: - expect( types ).to.have.property( 'get' ); - expect( types.get ).to.be.a( 'function' ); - expect( types ).to.have.property( '_renderURI' ); - expect( types._renderURI ).to.be.a( 'function' ); - }); - }); describe( 'URL Generation', function() { From 8a638ee2ada8d264eba2f285a30c55bdec9e9ee1 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 09:02:11 -0400 Subject: [PATCH 31/32] Fully remove CollectionRequest --- lib/shared/collection-request.js | 9 --------- lib/shared/wp-request.js | 6 +++--- lib/util/make-endpoint-request.js | 3 +-- tests/unit/lib/comments.js | 15 +-------------- tests/unit/lib/media.js | 4 +--- tests/unit/lib/pages.js | 17 +---------------- tests/unit/lib/posts.js | 17 +---------------- tests/unit/lib/taxonomies.js | 15 +-------------- tests/unit/lib/types.js | 4 +--- tests/unit/lib/users.js | 15 +-------------- tests/unit/wp.js | 4 +--- wp.js | 5 ++--- 12 files changed, 14 insertions(+), 100 deletions(-) delete mode 100644 lib/shared/collection-request.js diff --git a/lib/shared/collection-request.js b/lib/shared/collection-request.js deleted file mode 100644 index 29986fed..00000000 --- a/lib/shared/collection-request.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; -/** - * @module WP - * @submodule CollectionRequest - * @beta - */ -var WPRequest = require( './wp-request' ); - -module.exports = WPRequest; diff --git a/lib/shared/wp-request.js b/lib/shared/wp-request.js index 07fdfd60..1da54df9 100644 --- a/lib/shared/wp-request.js +++ b/lib/shared/wp-request.js @@ -151,9 +151,9 @@ function returnHeaders( result ) { * All terms listed in the arrays will be required (AND behavior). * * This method will not be called with any values unless we are handling - * a CollectionRequest or one of its descendants; however, since parameter - * handling (and therefore `_renderQuery()`) are part of WPRequest itself, - * this helper method lives here alongside the code where it is used. + * an endpoint with the filter mixin; however, since parameter handling + * (and therefore `_renderQuery()`) are part of WPRequest itself, this + * helper method lives here alongside the code where it is used. * * @example * prepareTaxonomies({ diff --git a/lib/util/make-endpoint-request.js b/lib/util/make-endpoint-request.js index 84321341..3e4f63ab 100644 --- a/lib/util/make-endpoint-request.js +++ b/lib/util/make-endpoint-request.js @@ -2,7 +2,6 @@ var inherit = require( 'util' ).inherits; var WPRequest = require( '../shared/wp-request' ); -var CollectionRequest = require( '../shared/collection-request' ); var mixins = require( '../mixins' ); function makeEndpointRequest( handlerSpec, resource, namespace ) { @@ -27,7 +26,7 @@ function makeEndpointRequest( handlerSpec, resource, namespace ) { .namespace( namespace ); } - inherit( EndpointRequest, CollectionRequest ); + inherit( EndpointRequest, WPRequest ); // Mix in all available shortcut methods for GET request query parameters that // are valid within this endpoint tree diff --git a/tests/unit/lib/comments.js b/tests/unit/lib/comments.js index e59ec4cb..4fe98b76 100644 --- a/tests/unit/lib/comments.js +++ b/tests/unit/lib/comments.js @@ -2,7 +2,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.comments', function() { @@ -46,22 +45,10 @@ describe( 'wp.comments', function() { expect( comments._supportedMethods ).to.be.an( 'array' ); }); - it( 'should inherit CommentsRequest from CollectionRequest', function() { - expect( comments instanceof CollectionRequest ).to.be.true; + it( 'should inherit CommentsRequest from WPRequest', function() { expect( comments instanceof WPRequest ).to.be.true; }); - it( 'should inherit prototype methods from both ancestors', function() { - // Spot-check from CollectionRequest: - expect( comments ).to.have.property( 'param' ); - expect( comments.param ).to.be.a( 'function' ); - // From WPRequest: - expect( comments ).to.have.property( 'get' ); - expect( comments.get ).to.be.a( 'function' ); - expect( comments ).to.have.property( '_renderURI' ); - expect( comments._renderURI ).to.be.a( 'function' ); - }); - }); describe( 'path part setters', function() { diff --git a/tests/unit/lib/media.js b/tests/unit/lib/media.js index 29cc5301..dab2fd1b 100644 --- a/tests/unit/lib/media.js +++ b/tests/unit/lib/media.js @@ -2,7 +2,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.media', function() { @@ -46,8 +45,7 @@ describe( 'wp.media', function() { expect( media._supportedMethods ).to.be.an( 'array' ); }); - it( 'should inherit MediaRequest from CollectionRequest', function() { - expect( media instanceof CollectionRequest ).to.be.true; + it( 'should inherit MediaRequest from WPRequest', function() { expect( media instanceof WPRequest ).to.be.true; }); diff --git a/tests/unit/lib/pages.js b/tests/unit/lib/pages.js index 901273a5..65baa113 100644 --- a/tests/unit/lib/pages.js +++ b/tests/unit/lib/pages.js @@ -2,7 +2,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.pages', function() { @@ -46,24 +45,10 @@ describe( 'wp.pages', function() { expect( pages._supportedMethods ).to.be.an( 'array' ); }); - it( 'should inherit PagesRequest from CollectionRequest', function() { - expect( pages instanceof CollectionRequest ).to.be.true; + it( 'should inherit PagesRequest from WPRequest', function() { expect( pages instanceof WPRequest ).to.be.true; }); - it( 'should inherit prototype methods from both ancestors', function() { - // Spot-check from CollectionRequest: - expect( pages ).to.have.property( 'filter' ); - expect( pages.filter ).to.be.a( 'function' ); - expect( pages ).to.have.property( 'param' ); - expect( pages.param ).to.be.a( 'function' ); - // From WPRequest: - expect( pages ).to.have.property( 'get' ); - expect( pages.get ).to.be.a( 'function' ); - expect( pages ).to.have.property( '_renderURI' ); - expect( pages._renderURI ).to.be.a( 'function' ); - }); - }); describe( 'URL Generation', function() { diff --git a/tests/unit/lib/posts.js b/tests/unit/lib/posts.js index 3316fdbc..0caea493 100644 --- a/tests/unit/lib/posts.js +++ b/tests/unit/lib/posts.js @@ -2,7 +2,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.posts', function() { @@ -46,24 +45,10 @@ describe( 'wp.posts', function() { expect( posts._supportedMethods ).to.be.an( 'array' ); }); - it( 'should inherit PostsRequest from CollectionRequest', function() { - expect( posts instanceof CollectionRequest ).to.be.true; + it( 'should inherit PostsRequest from WPRequest', function() { expect( posts instanceof WPRequest ).to.be.true; }); - it( 'should inherit prototype methods from both ancestors', function() { - // Spot-check from CollectionRequest: - expect( posts ).to.have.property( 'filter' ); - expect( posts.filter ).to.be.a( 'function' ); - expect( posts ).to.have.property( 'param' ); - expect( posts.param ).to.be.a( 'function' ); - // From WPRequest: - expect( posts ).to.have.property( 'get' ); - expect( posts.get ).to.be.a( 'function' ); - expect( posts ).to.have.property( '_renderURI' ); - expect( posts._renderURI ).to.be.a( 'function' ); - }); - }); describe( 'path part setters', function() { diff --git a/tests/unit/lib/taxonomies.js b/tests/unit/lib/taxonomies.js index 1abb7765..26be3bfa 100644 --- a/tests/unit/lib/taxonomies.js +++ b/tests/unit/lib/taxonomies.js @@ -2,7 +2,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.taxonomies', function() { @@ -46,22 +45,10 @@ describe( 'wp.taxonomies', function() { expect( taxonomies._supportedMethods ).to.be.an( 'array' ); }); - it( 'should inherit TaxonomiesRequest from CollectionRequest', function() { - expect( taxonomies instanceof CollectionRequest ).to.be.true; + it( 'should inherit TaxonomiesRequest from WPRequest', function() { expect( taxonomies instanceof WPRequest ).to.be.true; }); - it( 'should inherit prototype methods from both ancestors', function() { - // Spot-check from CollectionRequest: - expect( taxonomies ).to.have.property( 'param' ); - expect( taxonomies.param ).to.be.a( 'function' ); - // From WPRequest: - expect( taxonomies ).to.have.property( 'get' ); - expect( taxonomies.get ).to.be.a( 'function' ); - expect( taxonomies ).to.have.property( '_renderURI' ); - expect( taxonomies._renderURI ).to.be.a( 'function' ); - }); - }); describe( 'path part setters', function() { diff --git a/tests/unit/lib/types.js b/tests/unit/lib/types.js index e7123d03..dfbea6a3 100644 --- a/tests/unit/lib/types.js +++ b/tests/unit/lib/types.js @@ -2,7 +2,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.types', function() { @@ -46,8 +45,7 @@ describe( 'wp.types', function() { expect( types._supportedMethods ).to.be.an( 'array' ); }); - it( 'should inherit PostsRequest from CollectionRequest', function() { - expect( types instanceof CollectionRequest ).to.be.true; + it( 'should inherit PostsRequest from WPRequest', function() { expect( types instanceof WPRequest ).to.be.true; }); diff --git a/tests/unit/lib/users.js b/tests/unit/lib/users.js index b2571fa7..0c6d457c 100644 --- a/tests/unit/lib/users.js +++ b/tests/unit/lib/users.js @@ -2,7 +2,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var CollectionRequest = require( '../../../lib/shared/collection-request' ); var WPRequest = require( '../../../lib/shared/wp-request' ); describe( 'wp.users', function() { @@ -46,22 +45,10 @@ describe( 'wp.users', function() { expect( users._supportedMethods ).to.be.an( 'array' ); }); - it( 'should inherit UsersRequest from CollectionRequest', function() { - expect( users instanceof CollectionRequest ).to.be.true; + it( 'should inherit UsersRequest from WPRequest', function() { expect( users instanceof WPRequest ).to.be.true; }); - it( 'should inherit prototype methods from both ancestors', function() { - // Spot-check from CollectionRequest: - expect( users ).to.have.property( 'param' ); - expect( users.param ).to.be.a( 'function' ); - // From WPRequest: - expect( users ).to.have.property( 'get' ); - expect( users.get ).to.be.a( 'function' ); - expect( users ).to.have.property( '_renderURI' ); - expect( users._renderURI ).to.be.a( 'function' ); - }); - }); describe( '.me()', function() { diff --git a/tests/unit/wp.js b/tests/unit/wp.js index 91dd2e4f..f0ff245b 100644 --- a/tests/unit/wp.js +++ b/tests/unit/wp.js @@ -4,7 +4,6 @@ var expect = require( 'chai' ).expect; var WP = require( '../../' ); // Constructors, for use with instanceof checks -var CollectionRequest = require( '../../lib/shared/collection-request' ); var WPRequest = require( '../../lib/shared/wp-request' ); describe( 'wp', function() { @@ -110,10 +109,9 @@ describe( 'wp', function() { expect( request._renderURI() ).to.equal( 'http://my.site.com/wp-json/custom/endpoint' ); }); - it( 'creates a CollectionRequest object', function() { + it( 'creates a WPRequest object', function() { var pathRequest = site.root( 'some/collection/endpoint' ); expect( pathRequest instanceof WPRequest ).to.be.true; - expect( pathRequest instanceof CollectionRequest ).to.be.true; }); it( 'inherits options from the parent WP instance', function() { diff --git a/wp.js b/wp.js index 32134acc..b829b760 100644 --- a/wp.js +++ b/wp.js @@ -29,7 +29,6 @@ var defaults = { }; // Pull in base module constructors -var CollectionRequest = require( './lib/shared/collection-request' ); var WPRequest = require( './lib/shared/wp-request' ); /** @@ -118,13 +117,13 @@ WP.prototype.url = function( url ) { * * @method root * @param {String} [relativePath] An endpoint-relative path to which to bind the request - * @return {CollectionRequest|WPRequest} A request object + * @return {WPRequest} A request object */ WP.prototype.root = function( relativePath ) { relativePath = relativePath || ''; var options = extend( {}, this._options ); // Request should be - var request = new CollectionRequest( options ); + var request = new WPRequest( options ); // Set the path template to the string passed in request._path = { '0': relativePath }; From e5cfd50f3fab7bac19e1f2098ce43224b5600316 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 14 Jun 2016 21:12:00 -0400 Subject: [PATCH 32/32] Rename and rearrange files in lib and test directories - All route handler tests now go in route-handlers/, since they are not testing code from lib/ - All key files in the handler generation flow have been moved out of util/ and up into lib/, since they are now the core functionality of the library - All files in lib/* root have been renamed to remove verbs, exposing the operative methods as named exports - WPRequest folder has been renamed to constructors/ --- lib/{shared => constructors}/wp-request.js | 0 .../parse-route-string.js => endpoint-factories.js} | 8 +++++--- .../make-endpoint-request.js => endpoint-request.js} | 10 ++++++---- ...enerate-path-part-setter.js => path-part-setter.js} | 6 ++++-- lib/{util => }/resource-handler-spec.js | 4 ++-- lib/{util => }/route-tree.js | 4 ++-- tests/integration/categories.js | 2 +- tests/integration/comments.js | 2 +- tests/integration/pages.js | 2 +- tests/integration/posts.js | 2 +- tests/integration/tags.js | 2 +- tests/unit/lib/{shared => constructors}/wp-request.js | 4 ++-- tests/unit/lib/mixins/filters.js | 2 +- tests/unit/lib/mixins/parameters.js | 2 +- tests/unit/lib/{util => }/route-tree.js | 4 ++-- tests/unit/{lib => route-handlers}/comments.js | 2 +- tests/unit/{lib => route-handlers}/media.js | 2 +- tests/unit/{lib => route-handlers}/pages.js | 2 +- tests/unit/{lib => route-handlers}/posts.js | 2 +- tests/unit/{lib => route-handlers}/taxonomies.js | 2 +- tests/unit/{lib => route-handlers}/types.js | 2 +- tests/unit/{lib => route-handlers}/users.js | 2 +- tests/unit/wp.js | 2 +- wp.js | 6 +++--- 24 files changed, 41 insertions(+), 35 deletions(-) rename lib/{shared => constructors}/wp-request.js (100%) rename lib/{util/parse-route-string.js => endpoint-factories.js} (86%) rename lib/{util/make-endpoint-request.js => endpoint-request.js} (87%) rename lib/{util/generate-path-part-setter.js => path-part-setter.js} (96%) rename lib/{util => }/resource-handler-spec.js (96%) rename lib/{util => }/route-tree.js (98%) rename tests/unit/lib/{shared => constructors}/wp-request.js (99%) rename tests/unit/lib/{util => }/route-tree.js (95%) rename tests/unit/{lib => route-handlers}/comments.js (98%) rename tests/unit/{lib => route-handlers}/media.js (97%) rename tests/unit/{lib => route-handlers}/pages.js (98%) rename tests/unit/{lib => route-handlers}/posts.js (98%) rename tests/unit/{lib => route-handlers}/taxonomies.js (96%) rename tests/unit/{lib => route-handlers}/types.js (96%) rename tests/unit/{lib => route-handlers}/users.js (97%) diff --git a/lib/shared/wp-request.js b/lib/constructors/wp-request.js similarity index 100% rename from lib/shared/wp-request.js rename to lib/constructors/wp-request.js diff --git a/lib/util/parse-route-string.js b/lib/endpoint-factories.js similarity index 86% rename from lib/util/parse-route-string.js rename to lib/endpoint-factories.js index 69725bfb..19cc8fd8 100644 --- a/lib/util/parse-route-string.js +++ b/lib/endpoint-factories.js @@ -6,7 +6,7 @@ var extend = require( 'node.extend' ); var createResourceHandlerSpec = require( './resource-handler-spec' ).create; -var makeEndpointRequest = require( './make-endpoint-request' ); +var createEndpointRequest = require( './endpoint-request' ).create; /** * Given an array of route definitions and a specific namespace for those routes, @@ -25,7 +25,7 @@ function generateEndpointFactories( namespace, routeDefinitions ) { var handlerSpec = createResourceHandlerSpec( routeDefinitions[ resource ], resource ); - var EndpointRequest = makeEndpointRequest( handlerSpec, resource, namespace ); + var EndpointRequest = createEndpointRequest( handlerSpec, resource, namespace ); // "handler" object is now fully prepared; create the factory method that // will instantiate and return a handler instance @@ -39,4 +39,6 @@ function generateEndpointFactories( namespace, routeDefinitions ) { }, {} ); } -module.exports = generateEndpointFactories; +module.exports = { + generate: generateEndpointFactories +}; diff --git a/lib/util/make-endpoint-request.js b/lib/endpoint-request.js similarity index 87% rename from lib/util/make-endpoint-request.js rename to lib/endpoint-request.js index 3e4f63ab..2937b5e2 100644 --- a/lib/util/make-endpoint-request.js +++ b/lib/endpoint-request.js @@ -1,10 +1,10 @@ 'use strict'; var inherit = require( 'util' ).inherits; -var WPRequest = require( '../shared/wp-request' ); -var mixins = require( '../mixins' ); +var WPRequest = require( './constructors/wp-request' ); +var mixins = require( './mixins' ); -function makeEndpointRequest( handlerSpec, resource, namespace ) { +function createEndpointRequest( handlerSpec, resource, namespace ) { // Create the constructor function for this endpoint function EndpointRequest( options ) { @@ -51,4 +51,6 @@ function makeEndpointRequest( handlerSpec, resource, namespace ) { return EndpointRequest; } -module.exports = makeEndpointRequest; +module.exports = { + create: createEndpointRequest +}; diff --git a/lib/util/generate-path-part-setter.js b/lib/path-part-setter.js similarity index 96% rename from lib/util/generate-path-part-setter.js rename to lib/path-part-setter.js index fef780f8..48f4bf1a 100644 --- a/lib/util/generate-path-part-setter.js +++ b/lib/path-part-setter.js @@ -11,7 +11,7 @@ * @param {[type]} node [description] * @returns {[type]} [description] */ -function generatePathPartSetter( node ) { +function createPathPartSetter( node ) { // Local references to `node` properties used by returned functions var nodeLevel = node.level; var nodeName = node.names[ 0 ]; @@ -80,4 +80,6 @@ function generatePathPartSetter( node ) { } } -module.exports = generatePathPartSetter; +module.exports = { + create: createPathPartSetter +}; diff --git a/lib/util/resource-handler-spec.js b/lib/resource-handler-spec.js similarity index 96% rename from lib/util/resource-handler-spec.js rename to lib/resource-handler-spec.js index b08aa6ea..03e8dd5f 100644 --- a/lib/util/resource-handler-spec.js +++ b/lib/resource-handler-spec.js @@ -1,6 +1,6 @@ 'use strict'; -var generatePathPartSetter = require( './generate-path-part-setter' ); +var createPathPartSetter = require( './path-part-setter' ).create; function addLevelOption( levelsObj, level, obj ) { levelsObj[ level ] = levelsObj[ level ] || []; @@ -20,7 +20,7 @@ function assignSetterFnForNode( handler, node ) { // First level is set implicitly, no dedicated setter needed if ( node.level > 0 ) { - setterFn = generatePathPartSetter( node ); + setterFn = createPathPartSetter( node ); node.names.forEach(function( name ) { // camel-case the setter name diff --git a/lib/util/route-tree.js b/lib/route-tree.js similarity index 98% rename from lib/util/route-tree.js rename to lib/route-tree.js index 4a131293..3e878067 100644 --- a/lib/util/route-tree.js +++ b/lib/route-tree.js @@ -1,7 +1,7 @@ 'use strict'; -var namedGroupRegexp = require( './named-group-regexp' ); -var ensure = require( './ensure' ); +var namedGroupRegexp = require( './util/named-group-regexp' ); +var ensure = require( './util/ensure' ); /** * Method to use when reducing route components array. diff --git a/tests/integration/categories.js b/tests/integration/categories.js index 92d5f99e..d7ccca2a 100644 --- a/tests/integration/categories.js +++ b/tests/integration/categories.js @@ -10,7 +10,7 @@ var expect = chai.expect; var _ = require( 'lodash' ); var WP = require( '../../' ); -var WPRequest = require( '../../lib/shared/wp-request.js' ); +var WPRequest = require( '../../lib/constructors/wp-request.js' ); // Define some arrays to use ensuring the returned data is what we expect // it to be (e.g. an array of the names from categories on the first page) diff --git a/tests/integration/comments.js b/tests/integration/comments.js index 5673c990..5b811edb 100644 --- a/tests/integration/comments.js +++ b/tests/integration/comments.js @@ -10,7 +10,7 @@ chai.use( require( 'chai-as-promised' ) ); var expect = chai.expect; var WP = require( '../../' ); -var WPRequest = require( '../../lib/shared/wp-request.js' ); +var WPRequest = require( '../../lib/constructors/wp-request.js' ); // Define some arrays to use ensuring the returned data is what we expect // it to be (e.g. an array of the titles from posts on the first page) diff --git a/tests/integration/pages.js b/tests/integration/pages.js index 9b7fd038..b8cfc818 100644 --- a/tests/integration/pages.js +++ b/tests/integration/pages.js @@ -9,7 +9,7 @@ chai.use( require( 'chai-as-promised' ) ); var expect = chai.expect; var WP = require( '../../' ); -var WPRequest = require( '../../lib/shared/wp-request.js' ); +var WPRequest = require( '../../lib/constructors/wp-request.js' ); // Define some arrays to use ensuring the returned data is what we expect // it to be (e.g. an array of the titles from pages on the first page) diff --git a/tests/integration/posts.js b/tests/integration/posts.js index b885991b..d5e2b481 100644 --- a/tests/integration/posts.js +++ b/tests/integration/posts.js @@ -9,7 +9,7 @@ chai.use( require( 'chai-as-promised' ) ); var expect = chai.expect; var WP = require( '../../' ); -var WPRequest = require( '../../lib/shared/wp-request.js' ); +var WPRequest = require( '../../lib/constructors/wp-request.js' ); // Define some arrays to use ensuring the returned data is what we expect // it to be (e.g. an array of the titles from posts on the first page) diff --git a/tests/integration/tags.js b/tests/integration/tags.js index 28333542..d2141e92 100644 --- a/tests/integration/tags.js +++ b/tests/integration/tags.js @@ -9,7 +9,7 @@ chai.use( require( 'chai-as-promised' ) ); var expect = chai.expect; var WP = require( '../../' ); -var WPRequest = require( '../../lib/shared/wp-request.js' ); +var WPRequest = require( '../../lib/constructors/wp-request.js' ); // Define some arrays to use ensuring the returned data is what we expect // it to be (e.g. an array of the names from tags on the first page) diff --git a/tests/unit/lib/shared/wp-request.js b/tests/unit/lib/constructors/wp-request.js similarity index 99% rename from tests/unit/lib/shared/wp-request.js rename to tests/unit/lib/constructors/wp-request.js index 1c2ef25f..482c4ee9 100644 --- a/tests/unit/lib/shared/wp-request.js +++ b/tests/unit/lib/constructors/wp-request.js @@ -5,7 +5,7 @@ chai.use( require( 'sinon-chai' ) ); var sinon = require( 'sinon' ); var sandbox = require( 'sandboxed-module' ); -var WPRequest = require( '../../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../../lib/constructors/wp-request' ); var filterMixins = require( '../../../../lib/mixins/filters' ); describe( 'WPRequest', function() { @@ -405,7 +405,7 @@ describe( 'WPRequest', function() { beforeEach(function() { mockAgent = new MockAgent(); - SandboxedRequest = sandbox.require( '../../../../lib/shared/wp-request', { + SandboxedRequest = sandbox.require( '../../../../lib/constructors/wp-request', { requires: { 'superagent': mockAgent } diff --git a/tests/unit/lib/mixins/filters.js b/tests/unit/lib/mixins/filters.js index dd3bd7e7..440bedfa 100644 --- a/tests/unit/lib/mixins/filters.js +++ b/tests/unit/lib/mixins/filters.js @@ -4,7 +4,7 @@ var expect = require( 'chai' ).expect; var inherit = require( 'util' ).inherits; var filterMixins = require( '../../../../lib/mixins/filters' ); -var WPRequest = require( '../../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../../lib/constructors/wp-request' ); describe( 'mixins: filter', function() { var Req; diff --git a/tests/unit/lib/mixins/parameters.js b/tests/unit/lib/mixins/parameters.js index a7d4ba95..171e356c 100644 --- a/tests/unit/lib/mixins/parameters.js +++ b/tests/unit/lib/mixins/parameters.js @@ -4,7 +4,7 @@ var expect = require( 'chai' ).expect; var inherit = require( 'util' ).inherits; var parameterMixins = require( '../../../../lib/mixins/parameters' ); -var WPRequest = require( '../../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../../lib/constructors/wp-request' ); describe( 'mixins: parameters', function() { var Req; diff --git a/tests/unit/lib/util/route-tree.js b/tests/unit/lib/route-tree.js similarity index 95% rename from tests/unit/lib/util/route-tree.js rename to tests/unit/lib/route-tree.js index 24ff95b8..c0f92fdc 100644 --- a/tests/unit/lib/util/route-tree.js +++ b/tests/unit/lib/route-tree.js @@ -1,8 +1,8 @@ 'use strict'; var expect = require( 'chai' ).expect; -var routeTree = require( '../../../../lib/util/route-tree' ); -var endpointResponse = require( '../../../../lib/data/endpoint-response.json' ); +var routeTree = require( '../../../lib/route-tree' ); +var endpointResponse = require( '../../../lib/data/endpoint-response.json' ); describe( 'route-tree utility', function() { diff --git a/tests/unit/lib/comments.js b/tests/unit/route-handlers/comments.js similarity index 98% rename from tests/unit/lib/comments.js rename to tests/unit/route-handlers/comments.js index 4fe98b76..39f76e16 100644 --- a/tests/unit/lib/comments.js +++ b/tests/unit/route-handlers/comments.js @@ -2,7 +2,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var WPRequest = require( '../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../lib/constructors/wp-request' ); describe( 'wp.comments', function() { var site; diff --git a/tests/unit/lib/media.js b/tests/unit/route-handlers/media.js similarity index 97% rename from tests/unit/lib/media.js rename to tests/unit/route-handlers/media.js index dab2fd1b..816d5f7a 100644 --- a/tests/unit/lib/media.js +++ b/tests/unit/route-handlers/media.js @@ -2,7 +2,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var WPRequest = require( '../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../lib/constructors/wp-request' ); describe( 'wp.media', function() { var site; diff --git a/tests/unit/lib/pages.js b/tests/unit/route-handlers/pages.js similarity index 98% rename from tests/unit/lib/pages.js rename to tests/unit/route-handlers/pages.js index 65baa113..63620975 100644 --- a/tests/unit/lib/pages.js +++ b/tests/unit/route-handlers/pages.js @@ -2,7 +2,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var WPRequest = require( '../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../lib/constructors/wp-request' ); describe( 'wp.pages', function() { var site; diff --git a/tests/unit/lib/posts.js b/tests/unit/route-handlers/posts.js similarity index 98% rename from tests/unit/lib/posts.js rename to tests/unit/route-handlers/posts.js index 0caea493..54efa8f3 100644 --- a/tests/unit/lib/posts.js +++ b/tests/unit/route-handlers/posts.js @@ -2,7 +2,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var WPRequest = require( '../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../lib/constructors/wp-request' ); describe( 'wp.posts', function() { var site; diff --git a/tests/unit/lib/taxonomies.js b/tests/unit/route-handlers/taxonomies.js similarity index 96% rename from tests/unit/lib/taxonomies.js rename to tests/unit/route-handlers/taxonomies.js index 26be3bfa..c24d00c8 100644 --- a/tests/unit/lib/taxonomies.js +++ b/tests/unit/route-handlers/taxonomies.js @@ -2,7 +2,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var WPRequest = require( '../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../lib/constructors/wp-request' ); describe( 'wp.taxonomies', function() { var site; diff --git a/tests/unit/lib/types.js b/tests/unit/route-handlers/types.js similarity index 96% rename from tests/unit/lib/types.js rename to tests/unit/route-handlers/types.js index dfbea6a3..2690b0b3 100644 --- a/tests/unit/lib/types.js +++ b/tests/unit/route-handlers/types.js @@ -2,7 +2,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var WPRequest = require( '../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../lib/constructors/wp-request' ); describe( 'wp.types', function() { var site; diff --git a/tests/unit/lib/users.js b/tests/unit/route-handlers/users.js similarity index 97% rename from tests/unit/lib/users.js rename to tests/unit/route-handlers/users.js index 0c6d457c..facd2263 100644 --- a/tests/unit/lib/users.js +++ b/tests/unit/route-handlers/users.js @@ -2,7 +2,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../../wp' ); -var WPRequest = require( '../../../lib/shared/wp-request' ); +var WPRequest = require( '../../../lib/constructors/wp-request' ); describe( 'wp.users', function() { var site; diff --git a/tests/unit/wp.js b/tests/unit/wp.js index f0ff245b..d14028a7 100644 --- a/tests/unit/wp.js +++ b/tests/unit/wp.js @@ -4,7 +4,7 @@ var expect = require( 'chai' ).expect; var WP = require( '../../' ); // Constructors, for use with instanceof checks -var WPRequest = require( '../../lib/shared/wp-request' ); +var WPRequest = require( '../../lib/constructors/wp-request' ); describe( 'wp', function() { diff --git a/wp.js b/wp.js index b829b760..ce5b31bf 100644 --- a/wp.js +++ b/wp.js @@ -20,8 +20,8 @@ var extend = require( 'node.extend' ); // All valid routes in API v2 beta 11 var routes = require( './lib/data/endpoint-response.json' ).routes; -var buildRouteTree = require( './lib/util/route-tree' ).build; -var generateEndpointFactories = require( './lib/util/parse-route-string' ); +var buildRouteTree = require( './lib/route-tree' ).build; +var generateEndpointFactories = require( './lib/endpoint-factories' ).generate; var defaults = { username: '', @@ -29,7 +29,7 @@ var defaults = { }; // Pull in base module constructors -var WPRequest = require( './lib/shared/wp-request' ); +var WPRequest = require( './lib/constructors/wp-request' ); /** * The base constructor for the WP API service