From 6ed33bf48e7db7baea0cafccc59ac63cff63880d Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 18 Apr 2017 15:44:00 -0400 Subject: [PATCH 1/4] introduce common-gax --- packages/common-gax/package.json | 38 ++++ packages/common-gax/src/index.js | 26 +++ packages/common-gax/src/service.js | 157 ++++++++++++++ packages/common-gax/test/index.js | 38 ++++ packages/common-gax/test/service.js | 319 ++++++++++++++++++++++++++++ scripts/helpers.js | 1 + scripts/link-common.js | 3 + 7 files changed, 582 insertions(+) create mode 100644 packages/common-gax/package.json create mode 100644 packages/common-gax/src/index.js create mode 100644 packages/common-gax/src/service.js create mode 100644 packages/common-gax/test/index.js create mode 100644 packages/common-gax/test/service.js diff --git a/packages/common-gax/package.json b/packages/common-gax/package.json new file mode 100644 index 00000000000..99618c1948e --- /dev/null +++ b/packages/common-gax/package.json @@ -0,0 +1,38 @@ +{ + "name": "@google-cloud/common-gax", + "version": "0.0.0", + "author": "Google Inc.", + "description": "Common components for Cloud APIs Node.js Client Libraries that require google-gax", + "contributors": [ + { + "name": "Stephen Sawchuk", + "email": "sawchuk@gmail.com" + } + ], + "main": "./src/index.js", + "files": [ + "src", + "AUTHORS", + "CONTRIBUTORS", + "COPYING" + ], + "repository": "googlecloudplatform/google-cloud-node", + "dependencies": { + "@google-cloud/common": "^0.13.1", + "extend": "^3.0.0", + "stream-events": "^1.0.1", + "through2": "^2.0.3" + }, + "devDependencies": { + "mocha": "^3.2.0", + "proxyquire": "^1.7.10" + }, + "scripts": { + "publish-module": "node ../../scripts/publish.js common-gax", + "test": "mocha test/*.js" + }, + "license": "Apache-2.0", + "engines": { + "node": ">=4.0.0" + } +} diff --git a/packages/common-gax/src/index.js b/packages/common-gax/src/index.js new file mode 100644 index 00000000000..c82167df8c6 --- /dev/null +++ b/packages/common-gax/src/index.js @@ -0,0 +1,26 @@ +/*! + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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. + */ + +/*! + * @module commonGax + */ + +'use strict'; + +/** + * @type {module:commonGax/service} + */ +exports.Service = require('./service.js'); diff --git a/packages/common-gax/src/service.js b/packages/common-gax/src/service.js new file mode 100644 index 00000000000..fba017b34cb --- /dev/null +++ b/packages/common-gax/src/service.js @@ -0,0 +1,157 @@ +/*! + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 common = require('@google-cloud/common'); +var extend = require('extend'); +var streamEvents = require('stream-events'); +var through = require('through2'); +var util = require('util'); + +function GaxService(config, options) { + if (global.GCLOUD_SANDBOX_ENV) { + return; + } + + config = extend({ + scopes: config.module.ALL_SCOPES + }, config); + + this.api = {}; + this.module = config.module; + this.options = options; + + common.Service.call(this, config, options); +} + +util.inherits(GaxService, common.Service); + +/** + * Funnel all API requests through this method, to be sure we have a project ID. + * + * @param {object} config - Configuration object. + * @param {string} config.client - The gax client name. + * @param {object} config.gaxOpts - GAX options. + * @param {function} config.method - The gax method to call. + * @param {object} config.reqOpts - Request options. + * @param {function=} callback - The callback function. + */ +GaxService.prototype.request = function(config, callback) { + if (global.GCLOUD_SANDBOX_ENV) { + return; + } + + this.prepareRequest_(config, function(err, requestFn) { + if (err) { + callback(err); + return; + } + + requestFn(callback); + }); +}; + +/** + * Make a request as a stream. + * + * @param {object} config - Configuration object. + * @param {string} config.client - The gax client name. + * @param {object} config.gaxOpts - GAX options. + * @param {function} config.method - The gax method to call. + * @param {object} config.reqOpts - Request options. + * @return {stream} + */ +GaxService.prototype.requestStream = function(config) { + if (global.GCLOUD_SANDBOX_ENV) { + return through.obj(); + } + + var self = this; + + var gaxStream; + var stream = streamEvents(through.obj()); + + stream.abort = function() { + if (gaxStream && gaxStream.cancel) { + gaxStream.cancel(); + } + }; + + stream.once('reading', function() { + self.prepareRequest_(config, function(err, requestFn) { + if (err) { + stream.destroy(err); + return; + } + + gaxStream = requestFn(); + + gaxStream + .on('error', function(err) { + stream.destroy(err); + }) + .pipe(stream); + }); + }); + + return stream; +}; + +/** + * Prepare a request by instantiating and caching the GAX client. Project ID + * placeholder tokens will be replaced in the request options. + * + * @private + * + * @param {object} config - Configuration object. + * @param {string} config.client - The gax client name. + * @param {object} config.gaxOpts - GAX options. + * @param {function} config.method - The gax method to call. + * @param {object} config.reqOpts - Request options. + * @param {function=} callback - The callback function. + */ +GaxService.prototype.prepareRequest_ = function(config, callback) { + var self = this; + + this.getProjectId(function(err, projectId) { + if (err) { + callback(err); + return; + } + + var gaxClient = self.api[config.client]; + + if (!gaxClient) { + // Lazily instantiate client. + gaxClient = self.module(self.options)[config.client](self.options); + self.api[config.client] = gaxClient; + } + + var reqOpts = extend(true, {}, config.reqOpts); + reqOpts = common.util.replaceProjectIdToken(reqOpts, projectId); + + var requestFn = gaxClient[config.method].bind( + gaxClient, + reqOpts, + config.gaxOpts + ); + + callback(null, requestFn); + }); +}; + +module.exports = GaxService; \ No newline at end of file diff --git a/packages/common-gax/test/index.js b/packages/common-gax/test/index.js new file mode 100644 index 00000000000..f8eccde86f6 --- /dev/null +++ b/packages/common-gax/test/index.js @@ -0,0 +1,38 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 assert = require('assert'); +var proxyquire = require('proxyquire'); + +var fakeService = {}; + +describe('common-gax', function() { + var commonGax; + + before(function() { + commonGax = proxyquire('../src/index.js', { + './service.js': fakeService + }); + }); + + it('should correctly export the commonGax modules', function() { + assert.deepEqual(commonGax, { + Service: fakeService + }); + }); +}); diff --git a/packages/common-gax/test/service.js b/packages/common-gax/test/service.js new file mode 100644 index 00000000000..27e815cd125 --- /dev/null +++ b/packages/common-gax/test/service.js @@ -0,0 +1,319 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 assert = require('assert'); +var extend = require('extend'); +var proxyquire = require('proxyquire'); +var through = require('through2'); +var util = require('@google-cloud/common').util; + +var replaceProjectIdTokenOverride; +var fakeUtil = extend({}, util, { + replaceProjectIdToken: function(reqOpts) { + if (replaceProjectIdTokenOverride) { + return replaceProjectIdTokenOverride.apply(null, arguments); + } + + return reqOpts; + } +}); + +function FakeService() { + this.calledWith_ = arguments; +} + +describe('GaxService', function() { + var GaxService; + var gaxService; + + var MODULE = { + ALL_SCOPES: ['scope1, scope2'] + }; + var CONFIG = { + module: MODULE + }; + var OPTIONS = {}; + + before(function() { + GaxService = proxyquire('../src/service.js', { + '@google-cloud/common': { + Service: FakeService, + util: fakeUtil + } + }); + }); + + beforeEach(function() { + replaceProjectIdTokenOverride = null; + + gaxService = new GaxService(CONFIG, OPTIONS); + }); + + describe('instantiation', function() { + it('should initialize the API object', function() { + assert.deepEqual(gaxService.api, {}); + }); + + it('should localize the GAX modules', function() { + assert.strictEqual(gaxService.module, MODULE); + }); + + it('should localize the options', function() { + assert.strictEqual(gaxService.options, OPTIONS); + }); + + it('should inherit from Service', function() { + assert(gaxService instanceof FakeService); + + assert.deepEqual(gaxService.calledWith_[0], extend({ + scopes: MODULE.ALL_SCOPES + }, CONFIG)); + assert.strictEqual(gaxService.calledWith_[1], OPTIONS); + }); + }); + + describe('request', function() { + var REQ_CONFIG = {}; + + it('should return if in snippet sandbox', function(done) { + gaxService.prepareRequest_ = function() { + done(new Error('Should not have gotten project ID.')); + }; + + global.GCLOUD_SANDBOX_ENV = true; + var returnValue = gaxService.request(REQ_CONFIG, assert.ifError); + delete global.GCLOUD_SANDBOX_ENV; + + assert.strictEqual(returnValue, undefined); + done(); + }); + + it('should prepare the request', function(done) { + gaxService.prepareRequest_ = function(config) { + assert.strictEqual(config, REQ_CONFIG); + done(); + }; + + gaxService.request(REQ_CONFIG, assert.ifError); + }); + + it('should execute callback with error', function(done) { + var error = new Error('Error.'); + + gaxService.prepareRequest_ = function(config, callback) { + callback(error); + }; + + gaxService.request(REQ_CONFIG, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should execute the returned request function', function(done) { + gaxService.prepareRequest_ = function(config, callback) { + function preparedRequestFn(cb) { + cb(); // done() + } + + callback(null, preparedRequestFn); + }; + + gaxService.request(REQ_CONFIG, done); + }); + }); + + describe('requestStream', function() { + var REQ_CONFIG = {}; + var GAX_STREAM; + + beforeEach(function() { + GAX_STREAM = through(); + + gaxService.prepareRequest_ = function(config, callback) { + function preparedRequestFn() { + return GAX_STREAM; + } + + callback(null, preparedRequestFn); + }; + }); + + it('should return if in snippet sandbox', function(done) { + gaxService.prepareRequest_ = function() { + done(new Error('Should not have gotten project ID.')); + }; + + global.GCLOUD_SANDBOX_ENV = true; + var returnValue = gaxService.requestStream(REQ_CONFIG); + returnValue.emit('reading'); + delete global.GCLOUD_SANDBOX_ENV; + + assert(returnValue instanceof require('stream')); + done(); + }); + + it('should expose an abort function', function(done) { + GAX_STREAM.cancel = done; + + var requestStream = gaxService.requestStream(REQ_CONFIG); + requestStream.emit('reading'); + requestStream.abort(); + }); + + it('should prepare the request once reading', function(done) { + gaxService.prepareRequest_ = function(config) { + assert.strictEqual(config, REQ_CONFIG); + done(); + }; + + var requestStream = gaxService.requestStream(REQ_CONFIG); + requestStream.emit('reading'); + }); + + it('should destroy the stream with prepare error', function(done) { + var error = new Error('Error.'); + + gaxService.prepareRequest_ = function(config, callback) { + callback(error); + }; + + var requestStream = gaxService.requestStream(CONFIG); + requestStream.emit('reading'); + + requestStream.on('error', function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should destroy the stream with GAX error', function(done) { + var error = new Error('Error.'); + + var requestStream = gaxService.requestStream(CONFIG); + requestStream.emit('reading'); + + requestStream.on('error', function(err) { + assert.strictEqual(err, error); + done(); + }); + + GAX_STREAM.emit('error', error); + }); + }); + + describe('prepareRequest_', function() { + var CONFIG = { + client: 'client', + method: 'method', + reqOpts: { + a: 'b', + c: 'd' + }, + gaxOpts: {} + }; + + var PROJECT_ID = 'project-id'; + + beforeEach(function() { + gaxService.getProjectId = function(callback) { + callback(null, PROJECT_ID); + }; + + gaxService.api[CONFIG.client] = { + [CONFIG.method]: util.noop + }; + }); + + it('should get the project ID', function(done) { + gaxService.getProjectId = function() { + done(); + }; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + }); + + it('should return error if getting project ID failed', function(done) { + var error = new Error('Error.'); + + gaxService.getProjectId = function(callback) { + callback(error); + }; + + gaxService.prepareRequest_(CONFIG, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should initiate and cache the client', function() { + var fakeClient = { + [CONFIG.method]: util.noop + }; + + gaxService.module = function(options) { + assert.strictEqual(options, gaxService.options); + + return { + [CONFIG.client]: function(options) { + assert.strictEqual(options, gaxService.options); + return fakeClient; + } + }; + }; + + gaxService.api = {}; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + + assert.strictEqual(gaxService.api[CONFIG.client], fakeClient); + }); + + it('should use the cached client', function() { + gaxService.module = function() { + throw new Error('Should not re-instantiate a GAX client.'); + }; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + }); + + it('should replace the project ID token', function(done) { + var replacedReqOpts = {}; + + replaceProjectIdTokenOverride = function(reqOpts, projectId) { + assert.notStrictEqual(reqOpts, CONFIG.reqOpts); + assert.deepEqual(reqOpts, CONFIG.reqOpts); + assert.strictEqual(projectId, PROJECT_ID); + + return replacedReqOpts; + }; + + gaxService.api[CONFIG.client][CONFIG.method] = { + bind: function(gaxClient, reqOpts) { + assert.strictEqual(reqOpts, replacedReqOpts); + + setImmediate(done); + + return util.noop; + } + }; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + }); + }); +}); diff --git a/scripts/helpers.js b/scripts/helpers.js index 184a9754d68..e9b9bf22971 100644 --- a/scripts/helpers.js +++ b/scripts/helpers.js @@ -238,6 +238,7 @@ Module.prototype.runSnippetTests = function() { Module.prototype.runSystemTests = function() { var modulesExcludedFromSystemTests = [ 'common', + 'common-gax', 'common-grpc', 'error-reporting', 'google-cloud', diff --git a/scripts/link-common.js b/scripts/link-common.js index 814b9a8969e..2b2f2a2d9ce 100644 --- a/scripts/link-common.js +++ b/scripts/link-common.js @@ -29,14 +29,17 @@ var Module = require('./helpers').Module; var common = new Module('common'); +var commonGax = new Module('common-gax'); var commonGrpc = new Module('common-grpc'); var packages = Module.getAll(); common.link(); +commonGax.link(); commonGrpc.link(); packages.forEach(function(pkg) { pkg.link(common); + pkg.link(commonGax); pkg.link(commonGrpc); }); From 1617635ae6578a3ff0f20c1f9e5a1bdc11c4809b Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 18 Apr 2017 16:32:52 -0400 Subject: [PATCH 2/4] normalize line endings --- packages/common-gax/src/index.js | 52 +-- packages/common-gax/src/service.js | 312 +++++++------- packages/common-gax/test/index.js | 76 ++-- packages/common-gax/test/service.js | 638 ++++++++++++++-------------- 4 files changed, 539 insertions(+), 539 deletions(-) diff --git a/packages/common-gax/src/index.js b/packages/common-gax/src/index.js index c82167df8c6..f4185106ac8 100644 --- a/packages/common-gax/src/index.js +++ b/packages/common-gax/src/index.js @@ -1,26 +1,26 @@ -/*! - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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. - */ - -/*! - * @module commonGax - */ - -'use strict'; - -/** - * @type {module:commonGax/service} - */ -exports.Service = require('./service.js'); +/*! + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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. + */ + +/*! + * @module commonGax + */ + +'use strict'; + +/** + * @type {module:commonGax/service} + */ +exports.Service = require('./service.js'); diff --git a/packages/common-gax/src/service.js b/packages/common-gax/src/service.js index fba017b34cb..794af0d1bd6 100644 --- a/packages/common-gax/src/service.js +++ b/packages/common-gax/src/service.js @@ -1,157 +1,157 @@ -/*! - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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 common = require('@google-cloud/common'); -var extend = require('extend'); -var streamEvents = require('stream-events'); -var through = require('through2'); -var util = require('util'); - -function GaxService(config, options) { - if (global.GCLOUD_SANDBOX_ENV) { - return; - } - - config = extend({ - scopes: config.module.ALL_SCOPES - }, config); - - this.api = {}; - this.module = config.module; - this.options = options; - - common.Service.call(this, config, options); -} - -util.inherits(GaxService, common.Service); - -/** - * Funnel all API requests through this method, to be sure we have a project ID. - * - * @param {object} config - Configuration object. - * @param {string} config.client - The gax client name. - * @param {object} config.gaxOpts - GAX options. - * @param {function} config.method - The gax method to call. - * @param {object} config.reqOpts - Request options. - * @param {function=} callback - The callback function. - */ -GaxService.prototype.request = function(config, callback) { - if (global.GCLOUD_SANDBOX_ENV) { - return; - } - - this.prepareRequest_(config, function(err, requestFn) { - if (err) { - callback(err); - return; - } - - requestFn(callback); - }); -}; - -/** - * Make a request as a stream. - * - * @param {object} config - Configuration object. - * @param {string} config.client - The gax client name. - * @param {object} config.gaxOpts - GAX options. - * @param {function} config.method - The gax method to call. - * @param {object} config.reqOpts - Request options. - * @return {stream} - */ -GaxService.prototype.requestStream = function(config) { - if (global.GCLOUD_SANDBOX_ENV) { - return through.obj(); - } - - var self = this; - - var gaxStream; - var stream = streamEvents(through.obj()); - - stream.abort = function() { - if (gaxStream && gaxStream.cancel) { - gaxStream.cancel(); - } - }; - - stream.once('reading', function() { - self.prepareRequest_(config, function(err, requestFn) { - if (err) { - stream.destroy(err); - return; - } - - gaxStream = requestFn(); - - gaxStream - .on('error', function(err) { - stream.destroy(err); - }) - .pipe(stream); - }); - }); - - return stream; -}; - -/** - * Prepare a request by instantiating and caching the GAX client. Project ID - * placeholder tokens will be replaced in the request options. - * - * @private - * - * @param {object} config - Configuration object. - * @param {string} config.client - The gax client name. - * @param {object} config.gaxOpts - GAX options. - * @param {function} config.method - The gax method to call. - * @param {object} config.reqOpts - Request options. - * @param {function=} callback - The callback function. - */ -GaxService.prototype.prepareRequest_ = function(config, callback) { - var self = this; - - this.getProjectId(function(err, projectId) { - if (err) { - callback(err); - return; - } - - var gaxClient = self.api[config.client]; - - if (!gaxClient) { - // Lazily instantiate client. - gaxClient = self.module(self.options)[config.client](self.options); - self.api[config.client] = gaxClient; - } - - var reqOpts = extend(true, {}, config.reqOpts); - reqOpts = common.util.replaceProjectIdToken(reqOpts, projectId); - - var requestFn = gaxClient[config.method].bind( - gaxClient, - reqOpts, - config.gaxOpts - ); - - callback(null, requestFn); - }); -}; - +/*! + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 common = require('@google-cloud/common'); +var extend = require('extend'); +var streamEvents = require('stream-events'); +var through = require('through2'); +var util = require('util'); + +function GaxService(config, options) { + if (global.GCLOUD_SANDBOX_ENV) { + return; + } + + config = extend({ + scopes: config.module.ALL_SCOPES + }, config); + + this.api = {}; + this.module = config.module; + this.options = options; + + common.Service.call(this, config, options); +} + +util.inherits(GaxService, common.Service); + +/** + * Funnel all API requests through this method, to be sure we have a project ID. + * + * @param {object} config - Configuration object. + * @param {string} config.client - The gax client name. + * @param {object} config.gaxOpts - GAX options. + * @param {function} config.method - The gax method to call. + * @param {object} config.reqOpts - Request options. + * @param {function=} callback - The callback function. + */ +GaxService.prototype.request = function(config, callback) { + if (global.GCLOUD_SANDBOX_ENV) { + return; + } + + this.prepareRequest_(config, function(err, requestFn) { + if (err) { + callback(err); + return; + } + + requestFn(callback); + }); +}; + +/** + * Make a request as a stream. + * + * @param {object} config - Configuration object. + * @param {string} config.client - The gax client name. + * @param {object} config.gaxOpts - GAX options. + * @param {function} config.method - The gax method to call. + * @param {object} config.reqOpts - Request options. + * @return {stream} + */ +GaxService.prototype.requestStream = function(config) { + if (global.GCLOUD_SANDBOX_ENV) { + return through.obj(); + } + + var self = this; + + var gaxStream; + var stream = streamEvents(through.obj()); + + stream.abort = function() { + if (gaxStream && gaxStream.cancel) { + gaxStream.cancel(); + } + }; + + stream.once('reading', function() { + self.prepareRequest_(config, function(err, requestFn) { + if (err) { + stream.destroy(err); + return; + } + + gaxStream = requestFn(); + + gaxStream + .on('error', function(err) { + stream.destroy(err); + }) + .pipe(stream); + }); + }); + + return stream; +}; + +/** + * Prepare a request by instantiating and caching the GAX client. Project ID + * placeholder tokens will be replaced in the request options. + * + * @private + * + * @param {object} config - Configuration object. + * @param {string} config.client - The gax client name. + * @param {object} config.gaxOpts - GAX options. + * @param {function} config.method - The gax method to call. + * @param {object} config.reqOpts - Request options. + * @param {function=} callback - The callback function. + */ +GaxService.prototype.prepareRequest_ = function(config, callback) { + var self = this; + + this.getProjectId(function(err, projectId) { + if (err) { + callback(err); + return; + } + + var gaxClient = self.api[config.client]; + + if (!gaxClient) { + // Lazily instantiate client. + gaxClient = self.module(self.options)[config.client](self.options); + self.api[config.client] = gaxClient; + } + + var reqOpts = extend(true, {}, config.reqOpts); + reqOpts = common.util.replaceProjectIdToken(reqOpts, projectId); + + var requestFn = gaxClient[config.method].bind( + gaxClient, + reqOpts, + config.gaxOpts + ); + + callback(null, requestFn); + }); +}; + module.exports = GaxService; \ No newline at end of file diff --git a/packages/common-gax/test/index.js b/packages/common-gax/test/index.js index f8eccde86f6..7f82ed4f190 100644 --- a/packages/common-gax/test/index.js +++ b/packages/common-gax/test/index.js @@ -1,38 +1,38 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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 assert = require('assert'); -var proxyquire = require('proxyquire'); - -var fakeService = {}; - -describe('common-gax', function() { - var commonGax; - - before(function() { - commonGax = proxyquire('../src/index.js', { - './service.js': fakeService - }); - }); - - it('should correctly export the commonGax modules', function() { - assert.deepEqual(commonGax, { - Service: fakeService - }); - }); -}); +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 assert = require('assert'); +var proxyquire = require('proxyquire'); + +var fakeService = {}; + +describe('common-gax', function() { + var commonGax; + + before(function() { + commonGax = proxyquire('../src/index.js', { + './service.js': fakeService + }); + }); + + it('should correctly export the commonGax modules', function() { + assert.deepEqual(commonGax, { + Service: fakeService + }); + }); +}); diff --git a/packages/common-gax/test/service.js b/packages/common-gax/test/service.js index 27e815cd125..12e9d724b7d 100644 --- a/packages/common-gax/test/service.js +++ b/packages/common-gax/test/service.js @@ -1,319 +1,319 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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 assert = require('assert'); -var extend = require('extend'); -var proxyquire = require('proxyquire'); -var through = require('through2'); -var util = require('@google-cloud/common').util; - -var replaceProjectIdTokenOverride; -var fakeUtil = extend({}, util, { - replaceProjectIdToken: function(reqOpts) { - if (replaceProjectIdTokenOverride) { - return replaceProjectIdTokenOverride.apply(null, arguments); - } - - return reqOpts; - } -}); - -function FakeService() { - this.calledWith_ = arguments; -} - -describe('GaxService', function() { - var GaxService; - var gaxService; - - var MODULE = { - ALL_SCOPES: ['scope1, scope2'] - }; - var CONFIG = { - module: MODULE - }; - var OPTIONS = {}; - - before(function() { - GaxService = proxyquire('../src/service.js', { - '@google-cloud/common': { - Service: FakeService, - util: fakeUtil - } - }); - }); - - beforeEach(function() { - replaceProjectIdTokenOverride = null; - - gaxService = new GaxService(CONFIG, OPTIONS); - }); - - describe('instantiation', function() { - it('should initialize the API object', function() { - assert.deepEqual(gaxService.api, {}); - }); - - it('should localize the GAX modules', function() { - assert.strictEqual(gaxService.module, MODULE); - }); - - it('should localize the options', function() { - assert.strictEqual(gaxService.options, OPTIONS); - }); - - it('should inherit from Service', function() { - assert(gaxService instanceof FakeService); - - assert.deepEqual(gaxService.calledWith_[0], extend({ - scopes: MODULE.ALL_SCOPES - }, CONFIG)); - assert.strictEqual(gaxService.calledWith_[1], OPTIONS); - }); - }); - - describe('request', function() { - var REQ_CONFIG = {}; - - it('should return if in snippet sandbox', function(done) { - gaxService.prepareRequest_ = function() { - done(new Error('Should not have gotten project ID.')); - }; - - global.GCLOUD_SANDBOX_ENV = true; - var returnValue = gaxService.request(REQ_CONFIG, assert.ifError); - delete global.GCLOUD_SANDBOX_ENV; - - assert.strictEqual(returnValue, undefined); - done(); - }); - - it('should prepare the request', function(done) { - gaxService.prepareRequest_ = function(config) { - assert.strictEqual(config, REQ_CONFIG); - done(); - }; - - gaxService.request(REQ_CONFIG, assert.ifError); - }); - - it('should execute callback with error', function(done) { - var error = new Error('Error.'); - - gaxService.prepareRequest_ = function(config, callback) { - callback(error); - }; - - gaxService.request(REQ_CONFIG, function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should execute the returned request function', function(done) { - gaxService.prepareRequest_ = function(config, callback) { - function preparedRequestFn(cb) { - cb(); // done() - } - - callback(null, preparedRequestFn); - }; - - gaxService.request(REQ_CONFIG, done); - }); - }); - - describe('requestStream', function() { - var REQ_CONFIG = {}; - var GAX_STREAM; - - beforeEach(function() { - GAX_STREAM = through(); - - gaxService.prepareRequest_ = function(config, callback) { - function preparedRequestFn() { - return GAX_STREAM; - } - - callback(null, preparedRequestFn); - }; - }); - - it('should return if in snippet sandbox', function(done) { - gaxService.prepareRequest_ = function() { - done(new Error('Should not have gotten project ID.')); - }; - - global.GCLOUD_SANDBOX_ENV = true; - var returnValue = gaxService.requestStream(REQ_CONFIG); - returnValue.emit('reading'); - delete global.GCLOUD_SANDBOX_ENV; - - assert(returnValue instanceof require('stream')); - done(); - }); - - it('should expose an abort function', function(done) { - GAX_STREAM.cancel = done; - - var requestStream = gaxService.requestStream(REQ_CONFIG); - requestStream.emit('reading'); - requestStream.abort(); - }); - - it('should prepare the request once reading', function(done) { - gaxService.prepareRequest_ = function(config) { - assert.strictEqual(config, REQ_CONFIG); - done(); - }; - - var requestStream = gaxService.requestStream(REQ_CONFIG); - requestStream.emit('reading'); - }); - - it('should destroy the stream with prepare error', function(done) { - var error = new Error('Error.'); - - gaxService.prepareRequest_ = function(config, callback) { - callback(error); - }; - - var requestStream = gaxService.requestStream(CONFIG); - requestStream.emit('reading'); - - requestStream.on('error', function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should destroy the stream with GAX error', function(done) { - var error = new Error('Error.'); - - var requestStream = gaxService.requestStream(CONFIG); - requestStream.emit('reading'); - - requestStream.on('error', function(err) { - assert.strictEqual(err, error); - done(); - }); - - GAX_STREAM.emit('error', error); - }); - }); - - describe('prepareRequest_', function() { - var CONFIG = { - client: 'client', - method: 'method', - reqOpts: { - a: 'b', - c: 'd' - }, - gaxOpts: {} - }; - - var PROJECT_ID = 'project-id'; - - beforeEach(function() { - gaxService.getProjectId = function(callback) { - callback(null, PROJECT_ID); - }; - - gaxService.api[CONFIG.client] = { - [CONFIG.method]: util.noop - }; - }); - - it('should get the project ID', function(done) { - gaxService.getProjectId = function() { - done(); - }; - - gaxService.prepareRequest_(CONFIG, assert.ifError); - }); - - it('should return error if getting project ID failed', function(done) { - var error = new Error('Error.'); - - gaxService.getProjectId = function(callback) { - callback(error); - }; - - gaxService.prepareRequest_(CONFIG, function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should initiate and cache the client', function() { - var fakeClient = { - [CONFIG.method]: util.noop - }; - - gaxService.module = function(options) { - assert.strictEqual(options, gaxService.options); - - return { - [CONFIG.client]: function(options) { - assert.strictEqual(options, gaxService.options); - return fakeClient; - } - }; - }; - - gaxService.api = {}; - - gaxService.prepareRequest_(CONFIG, assert.ifError); - - assert.strictEqual(gaxService.api[CONFIG.client], fakeClient); - }); - - it('should use the cached client', function() { - gaxService.module = function() { - throw new Error('Should not re-instantiate a GAX client.'); - }; - - gaxService.prepareRequest_(CONFIG, assert.ifError); - }); - - it('should replace the project ID token', function(done) { - var replacedReqOpts = {}; - - replaceProjectIdTokenOverride = function(reqOpts, projectId) { - assert.notStrictEqual(reqOpts, CONFIG.reqOpts); - assert.deepEqual(reqOpts, CONFIG.reqOpts); - assert.strictEqual(projectId, PROJECT_ID); - - return replacedReqOpts; - }; - - gaxService.api[CONFIG.client][CONFIG.method] = { - bind: function(gaxClient, reqOpts) { - assert.strictEqual(reqOpts, replacedReqOpts); - - setImmediate(done); - - return util.noop; - } - }; - - gaxService.prepareRequest_(CONFIG, assert.ifError); - }); - }); -}); +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 assert = require('assert'); +var extend = require('extend'); +var proxyquire = require('proxyquire'); +var through = require('through2'); +var util = require('@google-cloud/common').util; + +var replaceProjectIdTokenOverride; +var fakeUtil = extend({}, util, { + replaceProjectIdToken: function(reqOpts) { + if (replaceProjectIdTokenOverride) { + return replaceProjectIdTokenOverride.apply(null, arguments); + } + + return reqOpts; + } +}); + +function FakeService() { + this.calledWith_ = arguments; +} + +describe('GaxService', function() { + var GaxService; + var gaxService; + + var MODULE = { + ALL_SCOPES: ['scope1, scope2'] + }; + var CONFIG = { + module: MODULE + }; + var OPTIONS = {}; + + before(function() { + GaxService = proxyquire('../src/service.js', { + '@google-cloud/common': { + Service: FakeService, + util: fakeUtil + } + }); + }); + + beforeEach(function() { + replaceProjectIdTokenOverride = null; + + gaxService = new GaxService(CONFIG, OPTIONS); + }); + + describe('instantiation', function() { + it('should initialize the API object', function() { + assert.deepEqual(gaxService.api, {}); + }); + + it('should localize the GAX modules', function() { + assert.strictEqual(gaxService.module, MODULE); + }); + + it('should localize the options', function() { + assert.strictEqual(gaxService.options, OPTIONS); + }); + + it('should inherit from Service', function() { + assert(gaxService instanceof FakeService); + + assert.deepEqual(gaxService.calledWith_[0], extend({ + scopes: MODULE.ALL_SCOPES + }, CONFIG)); + assert.strictEqual(gaxService.calledWith_[1], OPTIONS); + }); + }); + + describe('request', function() { + var REQ_CONFIG = {}; + + it('should return if in snippet sandbox', function(done) { + gaxService.prepareRequest_ = function() { + done(new Error('Should not have gotten project ID.')); + }; + + global.GCLOUD_SANDBOX_ENV = true; + var returnValue = gaxService.request(REQ_CONFIG, assert.ifError); + delete global.GCLOUD_SANDBOX_ENV; + + assert.strictEqual(returnValue, undefined); + done(); + }); + + it('should prepare the request', function(done) { + gaxService.prepareRequest_ = function(config) { + assert.strictEqual(config, REQ_CONFIG); + done(); + }; + + gaxService.request(REQ_CONFIG, assert.ifError); + }); + + it('should execute callback with error', function(done) { + var error = new Error('Error.'); + + gaxService.prepareRequest_ = function(config, callback) { + callback(error); + }; + + gaxService.request(REQ_CONFIG, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should execute the returned request function', function(done) { + gaxService.prepareRequest_ = function(config, callback) { + function preparedRequestFn(cb) { + cb(); // done() + } + + callback(null, preparedRequestFn); + }; + + gaxService.request(REQ_CONFIG, done); + }); + }); + + describe('requestStream', function() { + var REQ_CONFIG = {}; + var GAX_STREAM; + + beforeEach(function() { + GAX_STREAM = through(); + + gaxService.prepareRequest_ = function(config, callback) { + function preparedRequestFn() { + return GAX_STREAM; + } + + callback(null, preparedRequestFn); + }; + }); + + it('should return if in snippet sandbox', function(done) { + gaxService.prepareRequest_ = function() { + done(new Error('Should not have gotten project ID.')); + }; + + global.GCLOUD_SANDBOX_ENV = true; + var returnValue = gaxService.requestStream(REQ_CONFIG); + returnValue.emit('reading'); + delete global.GCLOUD_SANDBOX_ENV; + + assert(returnValue instanceof require('stream')); + done(); + }); + + it('should expose an abort function', function(done) { + GAX_STREAM.cancel = done; + + var requestStream = gaxService.requestStream(REQ_CONFIG); + requestStream.emit('reading'); + requestStream.abort(); + }); + + it('should prepare the request once reading', function(done) { + gaxService.prepareRequest_ = function(config) { + assert.strictEqual(config, REQ_CONFIG); + done(); + }; + + var requestStream = gaxService.requestStream(REQ_CONFIG); + requestStream.emit('reading'); + }); + + it('should destroy the stream with prepare error', function(done) { + var error = new Error('Error.'); + + gaxService.prepareRequest_ = function(config, callback) { + callback(error); + }; + + var requestStream = gaxService.requestStream(CONFIG); + requestStream.emit('reading'); + + requestStream.on('error', function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should destroy the stream with GAX error', function(done) { + var error = new Error('Error.'); + + var requestStream = gaxService.requestStream(CONFIG); + requestStream.emit('reading'); + + requestStream.on('error', function(err) { + assert.strictEqual(err, error); + done(); + }); + + GAX_STREAM.emit('error', error); + }); + }); + + describe('prepareRequest_', function() { + var CONFIG = { + client: 'client', + method: 'method', + reqOpts: { + a: 'b', + c: 'd' + }, + gaxOpts: {} + }; + + var PROJECT_ID = 'project-id'; + + beforeEach(function() { + gaxService.getProjectId = function(callback) { + callback(null, PROJECT_ID); + }; + + gaxService.api[CONFIG.client] = { + [CONFIG.method]: util.noop + }; + }); + + it('should get the project ID', function(done) { + gaxService.getProjectId = function() { + done(); + }; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + }); + + it('should return error if getting project ID failed', function(done) { + var error = new Error('Error.'); + + gaxService.getProjectId = function(callback) { + callback(error); + }; + + gaxService.prepareRequest_(CONFIG, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should initiate and cache the client', function() { + var fakeClient = { + [CONFIG.method]: util.noop + }; + + gaxService.module = function(options) { + assert.strictEqual(options, gaxService.options); + + return { + [CONFIG.client]: function(options) { + assert.strictEqual(options, gaxService.options); + return fakeClient; + } + }; + }; + + gaxService.api = {}; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + + assert.strictEqual(gaxService.api[CONFIG.client], fakeClient); + }); + + it('should use the cached client', function() { + gaxService.module = function() { + throw new Error('Should not re-instantiate a GAX client.'); + }; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + }); + + it('should replace the project ID token', function(done) { + var replacedReqOpts = {}; + + replaceProjectIdTokenOverride = function(reqOpts, projectId) { + assert.notStrictEqual(reqOpts, CONFIG.reqOpts); + assert.deepEqual(reqOpts, CONFIG.reqOpts); + assert.strictEqual(projectId, PROJECT_ID); + + return replacedReqOpts; + }; + + gaxService.api[CONFIG.client][CONFIG.method] = { + bind: function(gaxClient, reqOpts) { + assert.strictEqual(reqOpts, replacedReqOpts); + + setImmediate(done); + + return util.noop; + } + }; + + gaxService.prepareRequest_(CONFIG, assert.ifError); + }); + }); +}); From d52cde8b4f9dd2ffee5d53a46994f3fb7cd4735a Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 19 Apr 2017 14:34:21 -0400 Subject: [PATCH 3/4] fix docs stuff --- packages/common-gax/src/index.js | 4 ++-- packages/common-gax/src/service.js | 15 +++++++++++++++ scripts/docs/config.js | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/common-gax/src/index.js b/packages/common-gax/src/index.js index f4185106ac8..a6539ef151f 100644 --- a/packages/common-gax/src/index.js +++ b/packages/common-gax/src/index.js @@ -15,12 +15,12 @@ */ /*! - * @module commonGax + * @module common-gax */ 'use strict'; /** - * @type {module:commonGax/service} + * @type {module:common-gax/service} */ exports.Service = require('./service.js'); diff --git a/packages/common-gax/src/service.js b/packages/common-gax/src/service.js index 794af0d1bd6..b8b41888065 100644 --- a/packages/common-gax/src/service.js +++ b/packages/common-gax/src/service.js @@ -14,6 +14,10 @@ * limitations under the License. */ +/*! + * @module common-gax/service + */ + 'use strict'; var common = require('@google-cloud/common'); @@ -22,6 +26,17 @@ var streamEvents = require('stream-events'); var through = require('through2'); var util = require('util'); +/** + * GaxService is a base class, meant to be inherited from by a "service" that + * uses GAX. + * + * @constructor + * @alias module:common-gax/service + * + * @param {object} config - Configuration object. + * @param {*} config.module - The generated module. + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + */ function GaxService(config, options) { if (global.GCLOUD_SANDBOX_ENV) { return; diff --git a/scripts/docs/config.js b/scripts/docs/config.js index 8857ef3ede1..563617d0c56 100644 --- a/scripts/docs/config.js +++ b/scripts/docs/config.js @@ -23,6 +23,7 @@ module.exports = { TOC: 'toc.json', IGNORE: [ 'common', + 'common-gax', 'common-grpc', 'bigtable/src/mutation.js', 'datastore/src/entity.js', From 67fe1e4ce2f6ad60bf9aabee412f2e2b506a883b Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 19 Apr 2017 15:00:59 -0400 Subject: [PATCH 4/4] fix coverage --- packages/common-gax/src/service.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/common-gax/src/service.js b/packages/common-gax/src/service.js index b8b41888065..eb7e6ce82b1 100644 --- a/packages/common-gax/src/service.js +++ b/packages/common-gax/src/service.js @@ -38,10 +38,6 @@ var util = require('util'); * @param {object} options - [Configuration object](#/docs/?method=gcloud). */ function GaxService(config, options) { - if (global.GCLOUD_SANDBOX_ENV) { - return; - } - config = extend({ scopes: config.module.ALL_SCOPES }, config);