diff --git a/packages/common/src/grpc-service.js b/packages/common/src/grpc-service.js index dc34a30190f..d939832fa23 100644 --- a/packages/common/src/grpc-service.js +++ b/packages/common/src/grpc-service.js @@ -757,7 +757,9 @@ GrpcService.prototype.getGrpcCredentials_ = function(callback) { grpc.credentials.createFromGoogleCredential(authClient) ); - self.projectId = self.projectId || authClient.projectId; + if (!self.projectId || self.projectId === '{{projectId}}') { + self.projectId = authClient.projectId; + } callback(null, credentials); }); diff --git a/packages/common/src/util.js b/packages/common/src/util.js index 5ac88b61d8b..830d36a07ea 100644 --- a/packages/common/src/util.js +++ b/packages/common/src/util.js @@ -493,23 +493,45 @@ function decorateRequest(reqOpts, config) { delete reqOpts.json.autoPaginateVal; } - for (var opt in reqOpts) { - if (is.string(reqOpts[opt])) { - if (reqOpts[opt].indexOf('{{projectId}}') > -1) { - if (!config.projectId) { - throw util.missingProjectIdError; - } - reqOpts[opt] = reqOpts[opt].replace(/{{projectId}}/g, config.projectId); - } - } else if (is.object(reqOpts[opt])) { - decorateRequest(reqOpts[opt], config); + return util.replaceProjectIdToken(reqOpts, config.projectId); +} + +util.decorateRequest = decorateRequest; + +/** + * Populate the `{{projectId}}` placeholder. + * + * @throws {Error} If a projectId is required, but one is not provided. + * + * @param {*} - Any input value that may contain a placeholder. Arrays and + * objects will be looped. + * @param {string} projectId - A projectId. If not provided + * @return {*} - The original argument with all placeholders populated. + */ +function replaceProjectIdToken(value, projectId) { + if (is.array(value)) { + value = value.map(function(val) { + return replaceProjectIdToken(val, projectId); + }); + } + + if (is.object(value)) { + for (var opt in value) { + value[opt] = replaceProjectIdToken(value[opt], projectId); } } - return reqOpts; + if (is.string(value) && value.indexOf('{{projectId}}') > -1) { + if (!projectId) { + throw util.missingProjectIdError; + } + value = value.replace(/{{projectId}}/g, projectId); + } + + return value; } -util.decorateRequest = decorateRequest; +util.replaceProjectIdToken = replaceProjectIdToken; /** * Extend a global configuration object with user options provided at the time diff --git a/packages/common/test/grpc-service.js b/packages/common/test/grpc-service.js index 753cd84a949..6f91b8a847c 100644 --- a/packages/common/test/grpc-service.js +++ b/packages/common/test/grpc-service.js @@ -1807,6 +1807,16 @@ describe('GrpcService', function() { }); }); + it('should change placeholder projectId', function(done) { + grpcService.projectId = '{{projectId}}'; + + grpcService.getGrpcCredentials_(function(err) { + assert.ifError(err); + assert.strictEqual(grpcService.projectId, AUTH_CLIENT.projectId); + done(); + }); + }); + it('should not update projectId if it was not found', function(done) { grpcService.projectId = 'project-id'; diff --git a/packages/common/test/util.js b/packages/common/test/util.js index a904f3e0cc3..00135f2bb79 100644 --- a/packages/common/test/util.js +++ b/packages/common/test/util.js @@ -1275,46 +1275,97 @@ describe('common/util', function() { assert.strictEqual(decoratedReqOpts.json.autoPaginateVal, undefined); }); - describe('projectId placeholder', function() { - var PROJECT_ID = 'project-id'; + it('should decorate the request', function() { + var config = { + projectId: 'project-id' + }; + var reqOpts = {}; + var decoratedReqOpts = {}; + + utilOverrides.replaceProjectIdToken = function(reqOpts_, projectId) { + assert.strictEqual(reqOpts_, reqOpts); + assert.strictEqual(projectId, config.projectId); + return decoratedReqOpts; + }; - it('should replace any {{projectId}} it finds', function() { - assert.deepEqual(util.decorateRequest({ + assert.strictEqual( + util.decorateRequest(reqOpts, config), + decoratedReqOpts + ); + }); + }); + + describe('projectId placeholder', function() { + var PROJECT_ID = 'project-id'; + + it('should replace any {{projectId}} it finds', function() { + assert.deepEqual(util.replaceProjectIdToken({ + here: 'A {{projectId}} Z', + nested: { here: 'A {{projectId}} Z', nested: { + here: 'A {{projectId}} Z' + } + }, + array: [ + { here: 'A {{projectId}} Z', nested: { here: 'A {{projectId}} Z' - } + }, + nestedArray: [ + { + here: 'A {{projectId}} Z', + nested: { + here: 'A {{projectId}} Z' + } + } + ] } - }, { projectId: PROJECT_ID }), - { + ] + }, PROJECT_ID), + { + here: 'A ' + PROJECT_ID + ' Z', + nested: { here: 'A ' + PROJECT_ID + ' Z', nested: { + here: 'A ' + PROJECT_ID + ' Z' + } + }, + array: [ + { here: 'A ' + PROJECT_ID + ' Z', nested: { here: 'A ' + PROJECT_ID + ' Z' - } + }, + nestedArray: [ + { + here: 'A ' + PROJECT_ID + ' Z', + nested: { + here: 'A ' + PROJECT_ID + ' Z' + } + } + ] } - }); + ] }); + }); - it('should replace more than one {{projectId}}', function() { - assert.deepEqual(util.decorateRequest({ - here: 'A {{projectId}} M {{projectId}} Z', - }, { projectId: PROJECT_ID }), - { - here: 'A ' + PROJECT_ID + ' M ' + PROJECT_ID + ' Z' - }); + it('should replace more than one {{projectId}}', function() { + assert.deepEqual(util.replaceProjectIdToken({ + here: 'A {{projectId}} M {{projectId}} Z', + }, PROJECT_ID), + { + here: 'A ' + PROJECT_ID + ' M ' + PROJECT_ID + ' Z' }); + }); - it('should throw if it needs a projectId and cannot find it', function() { - assert.throws(function() { - util.decorateRequest({ - here: '{{projectId}}' - }); - }, new RegExp(util.missingProjectIdError)); - }); + it('should throw if it needs a projectId and cannot find it', function() { + assert.throws(function() { + util.replaceProjectIdToken({ + here: '{{projectId}}' + }); + }, new RegExp(util.missingProjectIdError)); }); });