Skip to content

Commit ba366a9

Browse files
Implement Service & ServiceObject classes
1 parent a93fda1 commit ba366a9

File tree

4 files changed

+1171
-0
lines changed

4 files changed

+1171
-0
lines changed

lib/common/service-object.js

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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+
// The `request` method may have been overridden to hold any special behavior.
139+
// Ensure we call the original `request` method.
140+
ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) {
141+
callback(err, resp);
142+
});
143+
};
144+
145+
/**
146+
* Check if the object exists.
147+
*
148+
* @param {function} callback - The callback function.
149+
* @param {?error} callback.err - An error returned while making this request.
150+
* @param {boolean} callback.exists - Whether the object exists or not.
151+
*/
152+
ServiceObject.prototype.exists = function(callback) {
153+
this.get(function(err) {
154+
if (err) {
155+
if (err.code === 404) {
156+
callback(null, false);
157+
} else {
158+
callback(err);
159+
}
160+
161+
return;
162+
}
163+
164+
callback(null, false);
165+
});
166+
};
167+
168+
/**
169+
* Get the object if it exists. Optionally have the object created if an options
170+
* object is provided with `autoCreate: true`.
171+
*
172+
* @param {object=} config - The configuration object that will be used to
173+
* create the object if necessary.
174+
* @param {boolean} config.autoCreate - Create the object if it doesn't already
175+
* exist.
176+
* @param {function} callback - The callback function.
177+
* @param {?error} callback.err - An error returned while making this request.
178+
* @param {object} callback.instance - The instance.
179+
* @param {object} callback.apiResponse - The full API response.
180+
*/
181+
ServiceObject.prototype.get = function(config, callback) {
182+
var self = this;
183+
184+
if (is.fn(config)) {
185+
callback = config;
186+
config = {};
187+
}
188+
189+
config = config || {};
190+
191+
var autoCreate = config.autoCreate && is.fn(this.create);
192+
delete config.autoCreate;
193+
194+
this.getMetadata(function(err, metadata) {
195+
if (err) {
196+
if (err.code === 404 && autoCreate) {
197+
if (!is.empty(config)) {
198+
self.create(config, callback);
199+
} else {
200+
self.create(callback);
201+
}
202+
return;
203+
}
204+
205+
callback(err, null, metadata);
206+
return;
207+
}
208+
209+
callback(null, self, metadata);
210+
});
211+
};
212+
213+
/**
214+
* Get the metadata of this object.
215+
*
216+
* @param {function} callback - The callback function.
217+
* @param {?error} callback.err - An error returned while making this request.
218+
* @param {object} callback.metadata - The metadata for this object.
219+
* @param {object} callback.apiResponse - The full API response.
220+
*/
221+
ServiceObject.prototype.getMetadata = function(callback) {
222+
var self = this;
223+
224+
var reqOpts = {
225+
uri: ''
226+
};
227+
228+
// The `request` method may have been overridden to hold any special behavior.
229+
// Ensure we call the original `request` method.
230+
ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) {
231+
if (err) {
232+
callback(err, null, resp);
233+
return;
234+
}
235+
236+
self.metadata = resp;
237+
238+
callback(null, self.metadata, resp);
239+
});
240+
};
241+
242+
/**
243+
* Set the metadata for this object.
244+
*
245+
* @param {object} metadata - The metadata to set on this object.
246+
* @param {function=} callback - The callback function.
247+
* @param {?error} callback.err - An error returned while making this request.
248+
* @param {object} callback.instance - The instance.
249+
* @param {object} callback.apiResponse - The full API response.
250+
*/
251+
ServiceObject.prototype.setMetadata = function(metadata, callback) {
252+
var self = this;
253+
254+
callback = callback || util.noop;
255+
256+
var reqOpts = {
257+
method: 'PATCH',
258+
uri: '',
259+
json: metadata
260+
};
261+
262+
// The `request` method may have been overridden to hold any special behavior.
263+
// Ensure we call the original `request` method.
264+
ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) {
265+
if (err) {
266+
callback(err, resp);
267+
return;
268+
}
269+
270+
self.metadata = resp;
271+
272+
callback(null, resp);
273+
});
274+
};
275+
276+
/**
277+
* Make an authenticated API request.
278+
*
279+
* @private
280+
*
281+
* @param {object} reqOpts - Request options that are passed to `request`.
282+
* @param {string} reqOpts.uri - A URI relative to the baseUrl.
283+
* @param {function} callback - The callback function passed to `request`.
284+
*/
285+
ServiceObject.prototype.request = function(reqOpts, callback) {
286+
var uriComponents = [
287+
this.baseUrl,
288+
this.id,
289+
reqOpts.uri
290+
];
291+
292+
reqOpts.uri = uriComponents
293+
.filter(exec('trim')) // Limit to non-empty strings.
294+
.map(function(uriComponent) {
295+
var trimSlashesRegex = /^\/*|\/*$/g;
296+
return uriComponent.replace(trimSlashesRegex, '');
297+
})
298+
.join('/');
299+
300+
this.parent.request(reqOpts, callback);
301+
};
302+
303+
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)