1 /**
  2  * @fileOverview Resource class definition
  3  */
  4 
  5 var _ = require('underscore')
  6   , ResponseError = require('./response_error')
  7   , EventEmitter = require('events').EventEmitter
  8   ;
  9 
 10 /**
 11  * Base class for objects repreenting resources in the spire api.  It is meant
 12  * to be extended by other classes.
 13  *
 14  * <p>Resources have methods for making requests to the spire api.  These methods
 15  * are defined with `Request.defineRequest`.  Note that this is a method on the
 16  * Request object itself, not the prototype.  It is used to create methods on
 17  * the prototype of Resource classes.</p>
 18  *
 19  * <p>The `Resource` class provides default requests for the `get`, `put`,
 20  * `delete`, and `post` methods.  These methods can be overwritten by
 21  * subclasses.</p>
 22  *
 23  * <p>Once a request method has been defined, it can be run with
 24  * <pre><code>
 25  *   resource.request(<request name>);
 26  * </code></pre>
 27  * </p>
 28  *
 29  * <p>Such request methods have no side effects, and return JSON objects direct
 30  * from the spire api.</p>
 31  *
 32  * @class Base class for Spire API Resources
 33  *
 34  * @constructor
 35  * @extends EventEmitter
 36  * @param {object} spire Spire object
 37  * @param {object} data Resource data from the spire api
 38  */
 39 function Resource(spire, data) {
 40   /**
 41    * Reference to spire object.
 42    */
 43   this.spire = spire;
 44 
 45   /**
 46    * Actual data from the spire.io api.
 47    */
 48   this.data = data;
 49 }
 50 
 51 Resource.prototype = new EventEmitter();
 52 
 53 module.exports = Resource;
 54 
 55 /**
 56  * Creates a request method on given object.
 57  *
 58  * `name` is the name of the request.  This is what gets passed to the `request`
 59  * method when actually calling the request.
 60  *
 61  * For instance, a request defined with
 62  * <pre><code>
 63  *   defineRequest(somePrototype, 'get', createGetReq);
 64  * </code></pre>
 65  *
 66  * is run by calling
 67  * <pre><code>
 68  *   resource.request('get', callback);
 69  * </code></pre>
 70  *
 71  * `fn` is a function that takes any number of arguments and returns a hash
 72  * describing the http request we are about to send.  Any arguments to this
 73  * function can be passed to the `request` method.
 74  *
 75  * For example, suppose `createGetReq` from above is the following function,
 76  * which takes an id number as argument as puts it in the query params:
 77  *
 78  * <pre><code>
 79  *   function createGetReq (id) {
 80  *     return {
 81  *       method: 'get',
 82  *       url: this.url(),
 83  *       query: { id: id },
 84  *      };
 85  *   };
 86  * </code></pre>
 87  *
 88  *  You would call that request like this:
 89  *  <code>resource.request('get', id, callback);</code>
 90  *
 91  * @param {object} obj Object to add the request method to.
 92  * @param {string} name Name of the request method
 93  * @param {function (*args)} fn Function taking any number of arguments, returning an object describing the HTTP request.
 94  */
 95 Resource.defineRequest = function (obj, name, fn) {
 96   obj['_req_' + name] = function () {
 97     var shred = this.spire.shred;
 98 
 99     var args = Array.prototype.slice.call(arguments);
100     var callback = args.pop();
101 
102     var req = fn.apply(this, args);
103 
104     return shred.request(req)
105       .on('error', function (res) {
106         var error = new ResponseError(res, req);
107         callback(error);
108       })
109       .on('success', function (res) {
110         callback(null, res.body.data);
111       });
112   };
113 };
114 
115 /**
116  * <p>Makes the request with the given name.
117  *
118  * <p>Arguments are the request name, followed by any number of arguments that will
119  * be passed to the function which creates the request description, and a
120  * callback.
121  */
122 Resource.prototype.request = function () {
123   var args = Array.prototype.slice.call(arguments);
124   var reqName = args.shift();
125   var cb = args[args.length - 1];
126   if (typeof this['_req_' + reqName] !== 'function') {
127     return cb(new Error("No request defined for " + reqName));
128   }
129   return this['_req_' + reqName].apply(this, args);
130 };
131 
132 /**
133  * <p>Gets the resource.
134  *
135  * <p>Default method that may be overwritten by subclasses.
136  *
137  * @param {function (err, resource)} cb Callback
138  */
139 Resource.prototype.get = function (cb) {
140   var resource = this;
141   this.request('get', function (err, data) {
142     if (err) return cb(err);
143     resource.data = data;
144     cb(null, resource);
145   });
146 };
147 
148 /**
149  * <p>Gets the resource if we have the get capability.  Otherwise just return the resource.
150  *
151  * <p>Default method that may be overwritten by subclasses.
152  *
153  * @param {function (err, resource)} cb Callback
154  */
155 Resource.prototype.getIfCapable = function(cb){
156   if(this.capability('get')){
157     this.get(cb);
158   } else {
159     cb(null, this);
160   }
161 }
162 
163 /**
164  * <p>Updates (puts to) the resource.
165  *
166  * <p>Default method that may be overwritten by subclasses.
167  *
168  * @param {object} data Resource data
169  * @param {function (err, resource)} cb Callback
170  */
171 Resource.prototype.update = function (data, cb) {
172   var resource = this;
173   this.request('update', data, function (err, data) {
174     if (err) return cb(err);
175     resource.data = data;
176     cb(null, resource);
177   });
178 };
179 
180 /**
181  * <p>Delete the resource.
182  *
183  * <p>Default method that may be overwritten by subclasses.
184  *
185  * @param {function (err, resource)} cb Callback
186  */
187 Resource.prototype['delete'] = function (cb) {
188   cb = cb || function (err) { if (err) { throw err; } };
189   var resource = this;
190   this.request('delete', function (err, data) {
191     if (err) return cb(err);
192     delete resource.data;
193     cb(null);
194   });
195 };
196 
197 /**
198  * Returns the url for the resource.
199  *
200  * @returns {string} url
201  */
202 Resource.prototype.url = function () {
203   return this.data.url;
204 };
205 
206 /**
207  * Returns the capabilities for the resource.
208  *
209  * @returns {object} Capabilities
210  */
211 Resource.prototype.capabilities = function () {
212   return this.data.capabilities;
213 };
214 
215 /**
216  * Returns the capability for the resource and method
217  *
218  * @returns {string} Capability
219  */
220 Resource.prototype.capability = function (method) {
221   return this.capabilities()[method];
222 };
223 
224 /**
225  * Returns the Authorization header for this resource, or for another capability
226  * if one is passed in.
227  *
228  * @param {Resource} [resource] Optional resource
229  * @param {string} method Method
230  * @returns {string} Authorization header
231  */
232 Resource.prototype.authorization = function (method, resource) {
233   var cap;
234   if (resource) {
235     if (typeof resource.capability === 'function') {
236       cap = resource.capability(method);
237     } else {
238       cap = resource.capabilities[method];
239     }
240   } else {
241     cap = this.capability(method);
242   }
243   return "Capability " + cap;
244 };
245 
246 /**
247  * Returns the resource schema for this resource the resource of a given name.
248  *
249  * @param {string} [name] Optional name of the resource schema to return
250  * @returns {string} Schema
251  */
252 Resource.prototype.schema = function (name) {
253   name = name || this.resourceName;
254 
255   if (!this.spire.api.schema) {
256     throw "No description object.  Run `spire.api.discover` first.";
257   }
258 
259   if (!this.spire.api.schema[name]) {
260     throw "No schema for resource " + name;
261   }
262 
263   return this.spire.api.schema[name];
264 };
265 
266 /**
267  * Returns the MIME type for this resource or the resource of a given name.
268  *
269  * @param {string} [name] Optional name of the resource MIME type to return
270  * @returns {string} MIME type
271  */
272 Resource.prototype.mediaType = function (name) {
273   return this.schema(name).mediaType;
274 };
275 
276 /**
277  * Requests
278  * These define API calls and have no side effects.  They can be run by calling
279  *     this.request(<request name>);
280  *
281  * The requests defined on the Resource class are defaults.  They can be
282  * overwritten by subclasses.
283  */
284 
285 /**
286  * Gets the resource.
287  * @name get
288  * @ignore
289  */
290 Resource.defineRequest(Resource.prototype, 'get', function () {
291   return {
292     method: 'get',
293     url: this.url(),
294     headers: {
295       'Authorization': this.authorization('get'),
296       'Accept': this.mediaType(),
297       'Content-Type': this.mediaType()
298     }
299   };
300 });
301 
302 /**
303  * Updates (puts) to the resouce.
304  * @name update
305  * @ignore
306  */
307 Resource.defineRequest(Resource.prototype, 'update', function (data) {
308   return {
309     method: 'put',
310     url: this.url(),
311     content: data,
312     headers: {
313       'Authorization': this.authorization('update'),
314       'Accept': this.mediaType(),
315       'Content-Type': this.mediaType()
316     }
317   };
318 });
319 
320 /**
321  * Deletes a resource.
322  * @name delete
323  * @ignore
324  */
325 Resource.defineRequest(Resource.prototype, 'delete', function () {
326   return {
327     method: 'delete',
328     url: this.url(),
329     headers: {
330       'Authorization': this.authorization('delete'),
331       'Accept': this.mediaType(),
332       'Content-Type': this.mediaType()
333     }
334   };
335 });
336