rink
Version:
Node wrapper around the hockeyapp API.
346 lines (281 loc) • 8.96 kB
JavaScript
var util = require("util");
var Promise = require("promise");
var _ = require("lodash");
var request = require("request");
function Api(def) {
this.def = def;
Object.keys(def.endpoints)
.forEach(this.registerEndpoint.bind(this));
}
module.exports = Api;
Api.prototype.name = function () {
return this.def.name;
};
Api.prototype.base = function () {
return this.def.base;
};
Api.prototype.version = function () {
return this.def.version;
};
Api.prototype.url = function (path) {
return this.base() + "/" + path;
};
Api.prototype.getDataType = function (def) {
var dataType = null;
if (this.def.dataTypes.hasOwnProperty(def.type)) {
var baseType = this.getDataType(this.def.dataTypes[def.type]);
dataType = new Api.DataType.Derived(this, def, baseType);
} else {
switch (def.type) {
case Api.DataType.File.NAME:
dataType = new Api.DataType.File(this, def);
break;
case Api.DataType.Enum.NAME:
dataType = new Api.DataType.Enum(this, def);
break;
case Api.DataType.Boolean.NAME:
dataType = new Api.DataType.Boolean(this, def);
break;
case Api.DataType.Scalar.NAME:
default:
dataType = new Api.DataType.Scalar(this, def);
break;
}
}
return dataType;
};
Api.prototype.registerEndpoint = function (name) {
this["$"+name] = function (params) {
var def = _.defaults({}, this.def.endpoints[name], this.def.defaults);
def.params = _.defaults({}, def.params, this.def.defaults.params);
var endpoint = new Api.Endpoint(this, name, def);
return endpoint.call(params);
}.bind(this);
};
Api.Endpoint = function (api, name, def) {
this.api = api;
this.name = name;
this.def = def;
}
Api.Endpoint.prototype.method = function () {
return this.def.method || "GET";
};
Api.Endpoint.prototype.contentType = function () {
return this.def.contentType || "multipart/form-data";
};
Api.Endpoint.prototype.responseType = function () {
return this.def.responseType || "application/json";
};
Api.Endpoint.prototype.url = function (pathParams) {
var tpl = _.template(this.def.path, {
interpolate: /:(\w+)/g
});
var path = tpl(pathParams);
return this.api.url(path);
};
Api.Endpoint.prototype.getDefaultParameterPlace = function () {
switch (this.def.contentType) {
case "multipart/form-data":
case "application/x-www-form-urlencoded":
return Api.Parameter.PLACE.BODY;
default:
return Api.Parameter.PLACE.QUERY;
}
};
Api.Endpoint.prototype.getParameter = function (name) {
if (this.def.params.hasOwnProperty(name)) {
return new Api.Parameter(this, name, this.def.params[name]);
}
throw new Error("unkown parameter: "+name);
};
Api.Endpoint.prototype.call = function (params) {
try {
params = params || {};
// check if any required params were omitted
Object.keys(this.def.params || {}).forEach(function (name) {
var parameter = this.getParameter(name);
if (parameter.required() && !params.hasOwnProperty(name)) {
throw new Error("missing required parameter: "+name);
}
}.bind(this));
// process provided args
var request = new Api.Request(this);
Object.keys(params).forEach(function (name) {
var parameter = this.getParameter(name);
parameter.process(request, params[name]);
}.bind(this));
return request.exec();
} catch (error) {
return Promise.reject(error);
}
};
Api.Parameter = function (endpoint, name, def) {
this.endpoint = endpoint;
this.name = name;
this.def = def;
};
Api.Parameter.PLACE = {
BODY: "body",
QUERY: "query",
HEADER: "header",
PATH: "path",
AUTH: "auth"
};
Api.Parameter.prototype.required = function () {
return !!this.def.required;
};
Api.Parameter.prototype.getDataType = function () {
return this.endpoint.api.getDataType(this.def);
};
Api.Parameter.prototype.process = function (request, value) {
var dataType = this.getDataType();
var serialized = dataType.serialize(value);
var internalName = this.def.internalName || this.name;
var place = this.def.place || this.endpoint.getDefaultParameterPlace();
switch (place) {
case Api.Parameter.PLACE.BODY:
request.addBodyParam(internalName, serialized);
break;
case Api.Parameter.PLACE.QUERY:
request.addQueryParam(internalName, serialized);
break;
case Api.Parameter.PLACE.HEADER:
request.addHeaderParam(internalName, serialized);
break;
case Api.Parameter.PLACE.PATH:
request.addPathParam(internalName, serialized);
break;
case Api.Parameter.PLACE.AUTH:
request.addAuthParam(internalName, serialized);
break;
}
};
Api.Request = function (endpoint) {
this.endpoint = endpoint;
this.headers = {};
this.path = {};
this.query = {};
this.body = {};
this.auth = null;
};
Api.Request.prototype.addHeaderParam = function (name, value) {
this.headers[name] = value;
return this;
};
Api.Request.prototype.addPathParam = function (name, value) {
this.path[name] = value;
return this;
};
Api.Request.prototype.addQueryParam = function (name, value) {
this.query[name] = value;
return this;
};
Api.Request.prototype.addBodyParam = function (name, value) {
this.body[name] = value;
return this;
};
Api.Request.prototype.addAuthParam = function (name, value) {
this.auth = this.auth || {};
this.auth[name] = value;
return this;
};
Api.Request.prototype.exec = function () {
// execute request
var requestOpts = {
url: this.endpoint.url(this.path),
method: this.endpoint.method()
};
requestOpts.qs = this.query;
requestOpts.headers = this.headers;
switch (this.endpoint.contentType()) {
case "multipart/form-data":
requestOpts.formData = this.body;
break;
case "application/json":
if (!this.endpoint.responseType()
|| this.endpoint.responseType() === this.endpoint.contentType()) {
requestOpts.json = this.body;
} else if (this.body) {
requestOpts.body = JSON.stringify(this.body);
}
break;
}
requestOpts.auth = this.auth;
return new Promise(function (resolve, reject) {
request(requestOpts, function (err, message, body) {
if (err) {
console.error(err);
reject(err);
} else if (message.statusCode >= 400) {
console.error(message.statusCode);
reject(JSON.parse(body));
} else {
resolve(JSON.parse(body));
}
});
}.bind(this));
};
Api.DataType = function (api, def) {
this.api = api;
this.def = def;
};
Api.DataType.Scalar = function (api, def) {
this.api = api;
this.def = def;
};
util.inherits(Api.DataType.Scalar, Api.DataType);
Api.DataType.Scalar.NAME = "scalar";
Api.DataType.Scalar.prototype.serialize = function (value) {
return value;
};
Api.DataType.Boolean = function (api, def) {
this.api = api;
this.def = def;
};
util.inherits(Api.DataType.Boolean, Api.DataType);
Api.DataType.Boolean.NAME = "boolean";
Api.DataType.Boolean.prototype.serialize = function (value) {
switch (value) {
case "1":
case "true":
case "yes":
return "true";
case "0":
case "false":
case "no":
return "false";
default:
throw new Error("invalid boolean value "+value);
}
};
Api.DataType.Enum = function (api, def) {
this.api = api;
this.def = def;
};
util.inherits(Api.DataType.Enum, Api.DataType);
Api.DataType.Enum.NAME = "enum";
Api.DataType.Enum.prototype.serialize = function (value) {
if (this.def.values.hasOwnProperty(value)) {
return this.def.values[value];
}
throw new Error("invalid enum value "+value);
};
Api.DataType.File = function (api, def) {
this.api = api;
this.def = def;
};
util.inherits(Api.DataType.File, Api.DataType);
Api.DataType.File.NAME = "file";
Api.DataType.File.prototype.serialize = function (value) {
var fs = require("fs");
return fs.createReadStream(value);
};
Api.DataType.Derived = function (api, def, baseType) {
this.api = api;
this.def = def;
this.baseType = baseType;
};
util.inherits(Api.DataType.Derived, Api.DataType);
Api.DataType.Derived.prototype.serialize = function (value) {
return this.baseType.serialize(value);
};