Skip to content

Commit 290eced

Browse files
committed
Merge pull request #928 from stephenplusplus/spp--ServiceObject-Introduction
Implement Service & ServiceObject classes
2 parents a93fda1 + 493dfba commit 290eced

File tree

4 files changed

+1185
-0
lines changed

4 files changed

+1185
-0
lines changed

lib/common/service-object.js

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
/*!
2+
* Copyright 2015 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*!
18+
* @module common/serviceObject
19+
*/
20+
21+
'use strict';
22+
23+
var exec = require('methmeth');
24+
var is = require('is');
25+
26+
/**
27+
* @type {module:common/util}
28+
* @private
29+
*/
30+
var util = require('./util.js');
31+
32+
/**
33+
* ServiceObject is a base class, meant to be inherited from by a "service
34+
* object," like a BigQuery dataset or Storage bucket.
35+
*
36+
* Most of the time, these objects share common functionality; they can be
37+
* created or deleted, and you can get or set their metadata.
38+
*
39+
* By inheriting from this class, a service object will be extended with these
40+
* shared behaviors. Note that any method can be overridden when the service
41+
* object requires specific behavior.
42+
*
43+
* @private
44+
*
45+
* @param {object} config - Configuration object.
46+
* @param {string} config.baseUrl - The base URL to make API requests to.
47+
* @param {string} config.createMethod - The method which creates this object.
48+
* @param {string} config.id - The identifier of the object. For example, the
49+
* name of a Storage bucket or Pub/Sub topic.
50+
* @param {object=} config.methods - A map of each method name that should be
51+
* inherited.
52+
* @param {object} config.parent - The parent service instance. For example, an
53+
* instance of Storage if the object is Bucket.
54+
*/
55+
function ServiceObject(config) {
56+
var self = this;
57+
58+
this.metadata = {};
59+
60+
this.baseUrl = config.baseUrl;
61+
this.parent = config.parent; // Parent class.
62+
this.id = config.id; // Name or ID (e.g. dataset ID, bucket name, etc.)
63+
this.createMethod = config.createMethod;
64+
65+
if (config.methods) {
66+
var allMethodNames = Object.keys(ServiceObject.prototype);
67+
allMethodNames
68+
.filter(function(methodName) {
69+
return (
70+
// All ServiceObjects need `request`.
71+
methodName !== 'request' &&
72+
73+
// The ServiceObject didn't redefine the method.
74+
self[methodName] === ServiceObject.prototype[methodName] &&
75+
76+
// This method isn't wanted.
77+
!config.methods[methodName]
78+
);
79+
})
80+
.forEach(function(methodName) {
81+
self[methodName] = undefined;
82+
});
83+
}
84+
}
85+
86+
/**
87+
* Create the object.
88+
*
89+
* @param {object=} options - Configuration object.
90+
* @param {function} callback - The callback function.
91+
* @param {?error} callback.err - An error returned while making this request.
92+
* @param {object} callback.instance - The instance.
93+
* @param {object} callback.apiResponse - The full API response.
94+
*/
95+
ServiceObject.prototype.create = function(options, callback) {
96+
var self = this;
97+
var args = [this.id];
98+
99+
if (is.fn(options)) {
100+
callback = options;
101+
}
102+
103+
if (is.object(options)) {
104+
args.push(options);
105+
}
106+
107+
// Wrap the callback to return *this* instance of the object, not the newly-
108+
// created one.
109+
function onCreate(err, instance, apiResponse) {
110+
if (err) {
111+
callback(err, null, apiResponse);
112+
return;
113+
}
114+
115+
self.metadata = instance.metadata;
116+
117+
callback(null, self, apiResponse);
118+
}
119+
120+
args.push(onCreate);
121+
122+
this.createMethod.apply(null, args);
123+
};
124+
125+
/**
126+
* Delete the object.
127+
*
128+
* @param {function=} callback - The callback function.
129+
* @param {?error} callback.err - An error returned while making this request.
130+
* @param {object} callback.apiResponse - The full API response.
131+
*/
132+
ServiceObject.prototype.delete = function(callback) {
133+
var reqOpts = {
134+
method: 'DELETE',
135+
uri: ''
136+
};
137+
138+
callback = callback || util.noop;
139+
140+
// The `request` method may have been overridden to hold any special behavior.
141+
// Ensure we call the original `request` method.
142+
ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) {
143+
callback(err, resp);
144+
});
145+
};
146+
147+
/**
148+
* Check if the object exists.
149+
*
150+
* @param {function} callback - The callback function.
151+
* @param {?error} callback.err - An error returned while making this request.
152+
* @param {boolean} callback.exists - Whether the object exists or not.
153+
*/
154+
ServiceObject.prototype.exists = function(callback) {
155+
this.get(function(err) {
156+
if (err) {
157+
if (err.code === 404) {
158+
callback(null, false);
159+
} else {
160+
callback(err);
161+
}
162+
163+
return;
164+
}
165+
166+
callback(null, true);
167+
});
168+
};
169+
170+
/**
171+
* Get the object if it exists. Optionally have the object created if an options
172+
* object is provided with `autoCreate: true`.
173+
*
174+
* @param {object=} config - The configuration object that will be used to
175+
* create the object if necessary.
176+
* @param {boolean} config.autoCreate - Create the object if it doesn't already
177+
* exist.
178+
* @param {function} callback - The callback function.
179+
* @param {?error} callback.err - An error returned while making this request.
180+
* @param {object} callback.instance - The instance.
181+
* @param {object} callback.apiResponse - The full API response.
182+
*/
183+
ServiceObject.prototype.get = function(config, callback) {
184+
var self = this;
185+
186+
if (is.fn(config)) {
187+
callback = config;
188+
config = {};
189+
}
190+
191+
config = config || {};
192+
193+
var autoCreate = config.autoCreate && is.fn(this.create);
194+
delete config.autoCreate;
195+
196+
this.getMetadata(function(err, metadata) {
197+
if (err) {
198+
if (err.code === 404 && autoCreate) {
199+
var args = [callback];
200+
201+
if (!is.empty(config)) {
202+
args.unshift(config);
203+
}
204+
205+
self.create.apply(self, args);
206+
return;
207+
}
208+
209+
callback(err, null, metadata);
210+
return;
211+
}
212+
213+
callback(null, self, metadata);
214+
});
215+
};
216+
217+
/**
218+
* Get the metadata of this object.
219+
*
220+
* @param {function} callback - The callback function.
221+
* @param {?error} callback.err - An error returned while making this request.
222+
* @param {object} callback.metadata - The metadata for this object.
223+
* @param {object} callback.apiResponse - The full API response.
224+
*/
225+
ServiceObject.prototype.getMetadata = function(callback) {
226+
var self = this;
227+
228+
var reqOpts = {
229+
uri: ''
230+
};
231+
232+
// The `request` method may have been overridden to hold any special behavior.
233+
// Ensure we call the original `request` method.
234+
ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) {
235+
if (err) {
236+
callback(err, null, resp);
237+
return;
238+
}
239+
240+
self.metadata = resp;
241+
242+
callback(null, self.metadata, resp);
243+
});
244+
};
245+
246+
/**
247+
* Set the metadata for this object.
248+
*
249+
* @param {object} metadata - The metadata to set on this object.
250+
* @param {function=} callback - The callback function.
251+
* @param {?error} callback.err - An error returned while making this request.
252+
* @param {object} callback.instance - The instance.
253+
* @param {object} callback.apiResponse - The full API response.
254+
*/
255+
ServiceObject.prototype.setMetadata = function(metadata, callback) {
256+
var self = this;
257+
258+
callback = callback || util.noop;
259+
260+
var reqOpts = {
261+
method: 'PATCH',
262+
uri: '',
263+
json: metadata
264+
};
265+
266+
// The `request` method may have been overridden to hold any special behavior.
267+
// Ensure we call the original `request` method.
268+
ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) {
269+
if (err) {
270+
callback(err, resp);
271+
return;
272+
}
273+
274+
self.metadata = resp;
275+
276+
callback(null, resp);
277+
});
278+
};
279+
280+
/**
281+
* Make an authenticated API request.
282+
*
283+
* @private
284+
*
285+
* @param {object} reqOpts - Request options that are passed to `request`.
286+
* @param {string} reqOpts.uri - A URI relative to the baseUrl.
287+
* @param {function} callback - The callback function passed to `request`.
288+
*/
289+
ServiceObject.prototype.request = function(reqOpts, callback) {
290+
var uriComponents = [
291+
this.baseUrl,
292+
this.id,
293+
reqOpts.uri
294+
];
295+
296+
reqOpts.uri = uriComponents
297+
.filter(exec('trim')) // Limit to non-empty strings.
298+
.map(function(uriComponent) {
299+
var trimSlashesRegex = /^\/*|\/*$/g;
300+
return uriComponent.replace(trimSlashesRegex, '');
301+
})
302+
.join('/');
303+
304+
this.parent.request(reqOpts, callback);
305+
};
306+
307+
module.exports = ServiceObject;

lib/common/service.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*!
2+
* Copyright 2015 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*!
18+
* @module common/service
19+
*/
20+
21+
'use strict';
22+
23+
/**
24+
* @type {module:common/util}
25+
* @private
26+
*/
27+
var util = require('./util.js');
28+
29+
/**
30+
* Service is a base class, meant to be inherited from by a "service," like
31+
* BigQuery or Storage.
32+
*
33+
* This handles making authenticated requests by exposing a `makeReq_` function.
34+
*
35+
* @param {object} config - Configuration object.
36+
* @param {string} config.baseUrl - The base URL to make API requests to.
37+
* @param {string[]} config.scopes - The scopes required for the request.
38+
* @param {object} options - [Configuration object](#/docs/?method=gcloud).
39+
*/
40+
function Service(config, options) {
41+
this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({
42+
scopes: config.scopes,
43+
credentials: options.credentials,
44+
keyFile: options.keyFilename,
45+
email: options.email
46+
});
47+
48+
this.authClient = this.makeAuthenticatedRequest.authClient;
49+
this.baseUrl = config.baseUrl;
50+
this.getCredentials = this.makeAuthenticatedRequest.getCredentials;
51+
this.projectId = options.projectId;
52+
this.projectIdRequired = config.projectIdRequired !== false;
53+
}
54+
55+
/**
56+
* Make an authenticated API request.
57+
*
58+
* @private
59+
*
60+
* @param {object} reqOpts - Request options that are passed to `request`.
61+
* @param {string} reqOpts.uri - A URI relative to the baseUrl.
62+
* @param {function} callback - The callback function passed to `request`.
63+
*/
64+
Service.prototype.request = function(reqOpts, callback) {
65+
var uriComponents = [
66+
this.baseUrl
67+
];
68+
69+
if (this.projectIdRequired) {
70+
uriComponents.push('projects');
71+
uriComponents.push(this.projectId);
72+
}
73+
74+
uriComponents.push(reqOpts.uri);
75+
76+
reqOpts.uri = uriComponents
77+
.map(function(uriComponent) {
78+
var trimSlashesRegex = /^\/*|\/*$/g;
79+
return uriComponent.replace(trimSlashesRegex, '');
80+
})
81+
.join('/')
82+
// Some URIs have colon separators.
83+
// Bad: https://.../projects/:list
84+
// Good: https://.../projects:list
85+
.replace(/\/:/g, ':');
86+
87+
this.makeAuthenticatedRequest(reqOpts, callback);
88+
};
89+
90+
module.exports = Service;

0 commit comments

Comments
 (0)