diff --git a/lib/discovery.js b/lib/discovery.js new file mode 100644 index 00000000000..4e0ae72e88c --- /dev/null +++ b/lib/discovery.js @@ -0,0 +1,263 @@ +// Copyright 2014-2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var generatorUtils = require('./generator_utils'); +var DefaultTransporter = generatorUtils.DefaultTransporter; +var buildurl = generatorUtils.buildurl; +var handleError = generatorUtils.handleError; +var async = require('async'); +var fs = require('fs'); +var url = require('url'); +var util = require('util'); +var createAPIRequest = require('./apirequest'); + +var transporter = new DefaultTransporter(); + +function getPathParams (params) { + var pathParams = []; + if (typeof params !== 'object') { + params = {}; + } + Object.keys(params).forEach(function (key) { + if (params[key].location === 'path') { + pathParams.push(key); + } + }); + return pathParams; +} + +/** + * Given a method schema, add a method to a target. + * + * @param {object} target The target to which to add the method. + * @param {object} schema The top-level schema that contains the rootUrl, etc. + * @param {object} method The method schema from which to generate the method. + * @param {object} context The context to add to the method. + */ +function makeMethod (schema, method, context) { + return function (params, callback) { + var url = buildurl(schema.rootUrl + schema.servicePath + method.path); + + var parameters = { + options: { + url: url.substring(1, url.length - 1), + method: method.httpMethod + }, + params: params, + requiredParams: method.parameterOrder || [], + pathParams: getPathParams(method.parameters), + context: context + }; + + if (method.mediaUpload && method.mediaUpload.protocols && + method.mediaUpload.protocols.simple && + method.mediaUpload.protocols.simple.path) { + var mediaUrl = buildurl( + schema.rootUrl + + method.mediaUpload.protocols.simple.path + ); + parameters.mediaUrl = mediaUrl.substring(1, mediaUrl.length - 1); + } + + return createAPIRequest(parameters, callback); + }; +} + +/** + * Given a schema, add methods to a target. + * + * @param {object} target The target to which to apply the methods. + * @param {object} rootSchema The top-level schema, so we don't lose track of it + * during recursion. + * @param {object} schema The current schema from which to extract methods. + * @param {object} context The context to add to each method. + */ +function applyMethodsFromSchema (target, rootSchema, schema, context) { + if (schema.methods) { + for (var name in schema.methods) { + var method = schema.methods[name]; + target[name] = makeMethod(rootSchema, method, context); + } + } +} + +/** + * Given a schema, add methods and resources to a target. + * + * @param {object} target The target to which to apply the schema. + * @param {object} rootSchema The top-level schema, so we don't lose track of it + * during recursion. + * @param {object} schema The current schema from which to extract methods and + * resources. + * @param {object} context The context to add to each method. + */ +function applySchema (target, rootSchema, schema, context) { + applyMethodsFromSchema(target, rootSchema, schema, context); + + if (schema.resources) { + for (var resourceName in schema.resources) { + var resource = schema.resources[resourceName]; + if (!target[resourceName]) { + target[resourceName] = {}; + } + applySchema(target[resourceName], rootSchema, resource, context); + } + } +} + +/** + * Generate and Endpoint from an endpoint schema object. + * + * @param {object} schema The schema from which to generate the Endpoint. + * @return Function The Endpoint. + */ +function makeEndpoint (schema) { + var Endpoint = function (options) { + var self = this; + self._options = options || {}; + + applySchema(self, schema, schema, self); + }; + return Endpoint; +} + +/** + * Discovery for discovering API endpoints + * @param {object} options Options for discovery + * @this {Discovery} + */ +function Discovery (options) { + this.options = options || {}; +} + +/** + * Log output of generator + * Works just like console.log + */ +Discovery.prototype.log = function () { + if (this.options && this.options.debug) { + console.log.apply(this, arguments); + } +}; + +/** + * Generate all APIs and return as in-memory object. + * + * @param {function} callback Callback when all APIs have been generated + * @throws {Error} If there is an error generating any of the APIs + */ +Discovery.prototype.discoverAllAPIs = function (discoveryUrl, callback) { + var self = this; + var headers = this.options.includePrivate ? {} : { 'X-User-Ip': '0.0.0.0' }; + transporter.request({ + uri: discoveryUrl, + headers: headers + }, function (err, resp) { + if (err) { + return handleError(err, callback); + } + + async.parallel(resp.items.map(function (api) { + return function (cb) { + self.discoverAPI(api.discoveryRestUrl, function (err, _api) { + if (err) { + return cb(err); + } + api.api = _api; + cb(null, api); + }); + }; + }), function (err, apis) { + if (err) { + return callback(err); + } + + var versionIndex = {}; + var apisIndex = {}; + + apis.forEach(function (api) { + if (!apisIndex[api.name]) { + versionIndex[api.name] = {}; + apisIndex[api.name] = function (options) { + var type = typeof options; + var version; + if (type === 'string') { + version = options; + options = {}; + } else if (type === 'object') { + version = options.version; + delete options.version; + } else { + throw new Error('Argument error: Accepts only string or object'); + } + try { + var Endpoint = versionIndex[api.name][version]; + var ep = new Endpoint(options); + ep.google = this; // for drive.google.transporter + return Object.freeze(ep); // create new & freeze + } catch (e) { + throw new Error(util.format('Unable to load endpoint %s("%s"): %s', + api.name, version, e.message)); + } + }; + } + versionIndex[api.name][api.version] = api.api; + }); + + return callback(null, apisIndex); + }); + }); +}; + +/** + * Generate API file given discovery URL + * @param {String} apiDiscoveryUrl URL or filename of discovery doc for API + * @param {function} callback Callback when successful write of API + * @throws {Error} If there is an error generating the API. + */ +Discovery.prototype.discoverAPI = function (apiDiscoveryUrl, callback) { + function _generate (err, resp) { + if (err) { + return handleError(err, callback); + } + return callback(null, makeEndpoint(resp)); + } + + var parts = url.parse(apiDiscoveryUrl); + + if (apiDiscoveryUrl && !parts.protocol) { + this.log('Reading from file ' + apiDiscoveryUrl); + try { + return fs.readFile(apiDiscoveryUrl, { + encoding: 'utf8' + }, function (err, file) { + _generate(err, JSON.parse(file)); + }); + } catch (err) { + return handleError(err, callback); + } + } else { + this.log('Requesting ' + apiDiscoveryUrl); + transporter.request({ + uri: apiDiscoveryUrl + }, _generate); + } +}; + +/** + * Export the Discovery object + * @type {Discovery} + */ +module.exports = Discovery; diff --git a/lib/generator.js b/lib/generator.js index 2c05ab8145a..274c2c52d7b 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -13,7 +13,10 @@ 'use strict'; -var DefaultTransporter = require('../lib/transporters'); +var generatorUtils = require('./generator_utils'); +var DefaultTransporter = generatorUtils.DefaultTransporter; +var buildurl = generatorUtils.buildurl; +var handleError = generatorUtils.handleError; var async = require('async'); var swig = require('swig'); var beautify = require('js-beautify').js_beautify; @@ -21,8 +24,6 @@ var path = require('path'); var mkdirp = require('mkdirp'); var fs = require('fs'); var url = require('url'); -var util = require('util'); -var createAPIRequest = require('./apirequest'); var argv = require('minimist')(process.argv.slice(2)); var args = argv._; @@ -57,23 +58,6 @@ var RESERVED_PARAMS = ['resource', 'media', 'auth']; var templateContents = fs.readFileSync(API_TEMPLATE, { encoding: 'utf8' }); var transporter = new DefaultTransporter(); -/** - * Build a string used to create a URL from the discovery doc provided URL. - * @param {String} input URL to build from - * @return {String} Resulting built URL - */ -function buildurl (input) { - return ('\'' + input + '\'') - // No * symbols - .replace(/\*/g, '') - // No + symbols - .replace(/\+/g, '') - // replace double slashes with single slash (except in https://) - .replace(/([^:]\/)\/+/g, '$1') - // No {/ symbols - .replace(/\{\//g, '/{'); -} - /** * A multi-line string is turned into one line * @param {string} str String to process @@ -130,7 +114,6 @@ function getSafeParamName (param) { * Disable auto-escaping its output * @type {Boolean} */ -buildurl.safe = true; swig.setFilter('buildurl', buildurl); swig.setFilter('getAPIs', getAPIs); swig.setFilter('oneLine', oneLine); @@ -139,112 +122,6 @@ swig.setFilter('getPathParams', getPathParams); swig.setFilter('getSafeParamName', getSafeParamName); swig.setDefaults({ loader: swig.loaders.fs(path.join(__dirname, '..', 'templates')) }); -/** - * Handle error object with callback - * @param {Error} err Error object to return in callback - * @param {Function=} callback Optional callback function - */ -function handleError (err, callback) { - if (callback && typeof callback === 'function') { - callback(err, null); - } -} - -/** - * Given a method schema, add a method to a target. - * - * @param {object} target The target to which to add the method. - * @param {object} schema The top-level schema that contains the rootUrl, etc. - * @param {object} method The method schema from which to generate the method. - * @param {object} context The context to add to the method. - */ -function makeMethod (schema, method, context) { - return function (params, callback) { - var url = buildurl(schema.rootUrl + schema.servicePath + method.path); - - var parameters = { - options: { - url: url.substring(1, url.length - 1), - method: method.httpMethod - }, - params: params, - requiredParams: method.parameterOrder || [], - pathParams: getPathParams(method.parameters), - context: context - }; - - if (method.mediaUpload && method.mediaUpload.protocols && - method.mediaUpload.protocols.simple && - method.mediaUpload.protocols.simple.path) { - var mediaUrl = buildurl( - schema.rootUrl + - method.mediaUpload.protocols.simple.path - ); - parameters.mediaUrl = mediaUrl.substring(1, mediaUrl.length - 1); - } - - return createAPIRequest(parameters, callback); - }; -} - -/** - * Given a schema, add methods to a target. - * - * @param {object} target The target to which to apply the methods. - * @param {object} rootSchema The top-level schema, so we don't lose track of it - * during recursion. - * @param {object} schema The current schema from which to extract methods. - * @param {object} context The context to add to each method. - */ -function applyMethodsFromSchema (target, rootSchema, schema, context) { - if (schema.methods) { - for (var name in schema.methods) { - var method = schema.methods[name]; - target[name] = makeMethod(rootSchema, method, context); - } - } -} - -/** - * Given a schema, add methods and resources to a target. - * - * @param {object} target The target to which to apply the schema. - * @param {object} rootSchema The top-level schema, so we don't lose track of it - * during recursion. - * @param {object} schema The current schema from which to extract methods and - * resources. - * @param {object} context The context to add to each method. - */ -function applySchema (target, rootSchema, schema, context) { - applyMethodsFromSchema(target, rootSchema, schema, context); - - if (schema.resources) { - for (var resourceName in schema.resources) { - var resource = schema.resources[resourceName]; - if (!target[resourceName]) { - target[resourceName] = {}; - } - applySchema(target[resourceName], rootSchema, resource, context); - } - } -} - -/** - * Generate and Endpoint from an endpoint schema object. - * - * @param {object} schema The schema from which to generate the Endpoint. - * @return Function The Endpoint. - */ -function makeEndpoint (schema) { - var Endpoint = function (options) { - var self = this; - self._options = options || {}; - - applySchema(self, schema, schema, self); - }; - return Endpoint; -} - /** * Generator for generating API endpoints * @param {object} options Options for generation @@ -343,110 +220,6 @@ Generator.prototype.generateAPI = function (apiDiscoveryUrl, callback) { } }; -/** - * Generate all APIs and return as in-memory object. - * - * @param {function} callback Callback when all APIs have been generated - * @throws {Error} If there is an error generating any of the APIs - */ -Generator.prototype.discoverAllAPIs = function (discoveryUrl, callback) { - var self = this; - var headers = this.options.includePrivate ? {} : { 'X-User-Ip': '0.0.0.0' }; - transporter.request({ - uri: discoveryUrl, - headers: headers - }, function (err, resp) { - if (err) { - return handleError(err, callback); - } - - async.parallel(resp.items.map(function (api) { - return function (cb) { - self.discoverAPI(api.discoveryRestUrl, function (err, _api) { - if (err) { - return cb(err); - } - api.api = _api; - cb(null, api); - }); - }; - }), function (err, apis) { - if (err) { - return callback(err); - } - - var versionIndex = {}; - var apisIndex = {}; - - apis.forEach(function (api) { - if (!apisIndex[api.name]) { - versionIndex[api.name] = {}; - apisIndex[api.name] = function (options) { - var type = typeof options; - var version; - if (type === 'string') { - version = options; - options = {}; - } else if (type === 'object') { - version = options.version; - delete options.version; - } else { - throw new Error('Argument error: Accepts only string or object'); - } - try { - var Endpoint = versionIndex[api.name][version]; - var ep = new Endpoint(options); - ep.google = this; // for drive.google.transporter - return Object.freeze(ep); // create new & freeze - } catch (e) { - throw new Error(util.format('Unable to load endpoint %s("%s"): %s', - api.name, version, e.message)); - } - }; - } - versionIndex[api.name][api.version] = api.api; - }); - - return callback(null, apisIndex); - }); - }); -}; - -/** - * Generate API file given discovery URL - * @param {String} apiDiscoveryUrl URL or filename of discovery doc for API - * @param {function} callback Callback when successful write of API - * @throws {Error} If there is an error generating the API. - */ -Generator.prototype.discoverAPI = function (apiDiscoveryUrl, callback) { - function _generate (err, resp) { - if (err) { - return handleError(err, callback); - } - return callback(null, makeEndpoint(resp)); - } - - var parts = url.parse(apiDiscoveryUrl); - - if (apiDiscoveryUrl && !parts.protocol) { - this.log('Reading from file ' + apiDiscoveryUrl); - try { - return fs.readFile(apiDiscoveryUrl, { - encoding: 'utf8' - }, function (err, file) { - _generate(err, JSON.parse(file)); - }); - } catch (err) { - return handleError(err, callback); - } - } else { - this.log('Requesting ' + apiDiscoveryUrl); - transporter.request({ - uri: apiDiscoveryUrl - }, _generate); - } -}; - /** * Export the Generator object * @type {Generator} diff --git a/lib/generator_utils.js b/lib/generator_utils.js new file mode 100644 index 00000000000..78f7ca93872 --- /dev/null +++ b/lib/generator_utils.js @@ -0,0 +1,52 @@ +// Copyright 2014-2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +/** + * Build a string used to create a URL from the discovery doc provided URL. + * @param {String} input URL to build from + * @return {String} Resulting built URL + */ +function buildurl (input) { + return ('\'' + input + '\'') + // No * symbols + .replace(/\*/g, '') + // No + symbols + .replace(/\+/g, '') + // replace double slashes with single slash (except in https://) + .replace(/([^:]\/)\/+/g, '$1') + // No {/ symbols + .replace(/\{\//g, '/{'); +} + +/** + * Disable auto-escaping its output + * @type {Boolean} + */ +buildurl.safe = true; + +/** + * Handle error object with callback + * @param {Error} err Error object to return in callback + * @param {Function=} callback Optional callback function + */ +function handleError (err, callback) { + if (callback && typeof callback === 'function') { + callback(err, null); + } +} + +exports.DefaultTransporter = require('../lib/transporters'); +exports.buildurl = buildurl; +exports.handleError = handleError; diff --git a/lib/googleapis.js b/lib/googleapis.js index be2002b9019..9e39ea56acd 100644 --- a/lib/googleapis.js +++ b/lib/googleapis.js @@ -16,8 +16,8 @@ var path = require('path'); var fs = require('fs'); var util = require('util'); -var Generator = require('./generator'); -var gen = new Generator({ debug: false, includePrivate: false }); +var Discovery = require('./discovery'); +var discovery = new Discovery({ debug: false, includePrivate: false }); /** * Load the apis from apis index file @@ -143,7 +143,7 @@ GoogleApis.prototype.addAPIs = function (apis) { GoogleApis.prototype.discover = function (url, callback) { var self = this; - gen.discoverAllAPIs(url, function (err, apis) { + discovery.discoverAllAPIs(url, function (err, apis) { if (err) { return callback(err); } @@ -178,7 +178,7 @@ GoogleApis.prototype.discoverAPI = function (path, options, callback) { if (!options) { options = {}; } - gen.discoverAPI(path, function (err, Endpoint) { + discovery.discoverAPI(path, function (err, Endpoint) { if (err) { return callback(err); } diff --git a/package.json b/package.json index a663bdc108b..8f03aef7fb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "googleapis", - "version": "5.2.0", + "version": "5.2.1", "author": "Google Inc.", "license": "Apache-2.0", "description": "Google APIs Client Library for Node.js", @@ -64,7 +64,8 @@ "gapitoken": "~0.1.5", "google-auth-library": "~0.9.7", "request": "~2.72.0", - "string-template": "~1.0.0" + "string-template": "~1.0.0", + "url": "^0.11.0" }, "devDependencies": { "codecov": "^1.0.1", @@ -78,8 +79,7 @@ "nock": "^8.0.0", "rimraf": "^2.5.2", "semistandard": "^7.0.5", - "swig": "^1.4.2", - "url": "^0.11.0" + "swig": "^1.4.2" }, "scripts": { "lint": "semistandard \"**/*.js\"", diff --git a/test/test.discover.js b/test/test.discover.js index 1bb9794a7fa..36ea22be0b3 100644 --- a/test/test.discover.js +++ b/test/test.discover.js @@ -20,7 +20,7 @@ var path = require('path'); describe('GoogleApis#discover', function () { it('should generate all apis', function (done) { - this.timeout(60000); + this.timeout(120000); var localApis = fs.readdirSync(path.join(__dirname, '../apis')); var google = new googleapis.GoogleApis();