Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2a8feb5
Added support for automatically converting to XML, if requested via A…
shelbys Jul 2, 2014
5f472be
Better support for alternate Content-Types
shelbys Jul 8, 2014
ff07add
Switch from xml2js to easyxml in order to avoid bugs
shelbys Jul 8, 2014
632a8ff
Switch from easyxml to js2xmlparser in order to avoid bugs
shelbys Jul 8, 2014
d75d469
Configured pretty-printing and date conversion to ISO for XML responses
shelbys Jul 8, 2014
8c448be
Avoid adding empty description for each API
shelbys Jul 8, 2014
9fbb27d
REVERT: Avoid adding empty description for each API
shelbys Jul 8, 2014
0ab4726
Merge remote-tracking branch 'upstream/master'
shelbys Jul 13, 2014
4e81d27
Upgraded to generate specs based on Swagger 1.2
shelbys Jul 13, 2014
aa1c4ce
Added support for errors property to specify possible errors that can…
shelbys Jul 14, 2014
fe02b78
Added description for 200 responseMessage
shelbys Jul 14, 2014
2c1a042
Added error-handling when unable to generate XML
shelbys Jul 16, 2014
c049f13
Corrected addDynamicBasePathGetter() to honor x-forwarded-proto in or…
shelbys Jul 19, 2014
7784b80
Merge remote-tracking branch 'upstream/master' into v2
shelbys Aug 5, 2014
7277023
Merge remote-tracking branch 'upstream/master' into v2
shelbys Aug 5, 2014
ecb7a07
Added support for notes
shelbys Aug 7, 2014
a9510bd
Added support for return types as Arrays instead of Strings
shelbys Aug 7, 2014
007e781
Corrected XML handling when root is an Array
shelbys Aug 7, 2014
ea52c91
Added missing docs for errors and notes
shelbys Aug 7, 2014
4ec15e4
Re-added tests for XML conversion, and added tests for handling retur…
shelbys Aug 7, 2014
ae824a4
Merge pull request #1 from shelbys/v2
shelbys Aug 7, 2014
56d3d19
Changed errorHandler() to honor options.remoting.errorHandler.handler…
shelbys Sep 6, 2014
a34132d
Number validation
Sep 17, 2014
0e14478
Merge remote-tracking branch 'upstream/master'
shelbys Oct 7, 2014
1a2f528
Corrected params for ['string']
shelbys Oct 8, 2014
03d4e9d
Changed to automatically handle splitting arrays of boolean, date, nu…
shelbys Oct 8, 2014
3a3da6b
Removed obsolete debug print
shelbys Oct 8, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
.DS_Store
.idea/
*.seed
*.log
*.csv
*.dat
*.iml
*.out
*.pid
*.swp
Expand Down
4 changes: 3 additions & 1 deletion lib/exports-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var debug = require('debug')('strong-remoting:exports-helper');
/*!
* Constants
*/
var PASSTHROUGH_OPTIONS = ['http', 'description'];
var PASSTHROUGH_OPTIONS = ['http', 'description', 'notes'];

/**
* @class A wrapper to make manipulating the exports object easier.
Expand Down Expand Up @@ -112,6 +112,7 @@ function method(fn, options) {
var path = options.path || options.name || fn.name || null;
var accepts = options.accepts || null;
var returns = options.returns || null;
var errors = options.errors || null;

if (!path) {
// TODO: Error.
Expand All @@ -122,6 +123,7 @@ function method(fn, options) {
fn.shared = true;
fn.accepts = accepts;
fn.returns = returns;
fn.errors = errors;

PASSTHROUGH_OPTIONS.forEach(function (key) {
if (options[key]) {
Expand Down
52 changes: 50 additions & 2 deletions lib/http-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ var EventEmitter = require('events').EventEmitter
, inherits = util.inherits
, assert = require('assert')
, Dynamic = require('./dynamic')
, SUPPORTED_TYPES = ['json', 'application/javascript', 'text/javascript'];
, js2xmlparser = require("js2xmlparser")
, SUPPORTED_TYPES = [
'application/json', 'application/javascript', 'application/xml',
'text/javascript', 'text/xml',
'json', 'xml',
'*/*'
];

/**
* Create a new `HttpContext` with the given `options`.
Expand Down Expand Up @@ -47,6 +53,7 @@ HttpContext.prototype.buildArgs = function (method) {
var ctx = this;
var accepts = method.accepts;
var returns = method.returns;
var errors = method.errors;

// build arguments from req and method options
accepts.forEach(function (o) {
Expand Down Expand Up @@ -101,7 +108,13 @@ HttpContext.prototype.buildArgs = function (method) {

// cast booleans and numbers
var dynamic;
var otype = (typeof o.type === 'string') && o.type.toLowerCase();
var type = typeof val;
var otype = null;
if (Array.isArray(o.type)) {
otype = '[' + o.type.join(',') + ']';
} else {
otype = (typeof o.type === 'string') && o.type.toLowerCase();
}

if(Dynamic.canConvert(otype)) {
dynamic = new Dynamic(val, ctx);
Expand Down Expand Up @@ -261,13 +274,48 @@ HttpContext.prototype.done = function () {

if(dataExists) {
switch(accepts) {
case '*/*':
case 'application/json':
case 'json':
res.json(data);
break;
case 'application/javascript':
case 'text/javascript':
res.jsonp(data);
break;
case 'application/xml':
case 'text/xml':
case 'xml':
if (accepts == 'application/xml') {
res.header('Content-Type', 'application/xml');
} else {
res.header('Content-Type', 'text/xml');
}
if (data === null) {
res.header('Content-Length', '7');
res.end('<null/>');
} else {
try {
var input = data;
if (Object.prototype.toString.call(input) === "[object Array]") {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Array.isArray()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ritch - I vaguely remember there being a reason Array.isArray() didn't work, but I can't remember for sure now

input = { result: data };
}
var xml = js2xmlparser('response', input, {
prettyPrinting: {
indentString: ' '
},
convertMap: {
"[object Date]": function (date) {
return date.toISOString();
}
}
});
res.send(xml);
} catch(e) {
res.send(500, e + "\n" + data);
}
}
break;
default:
// not acceptable
res.send(406);
Expand Down
2 changes: 2 additions & 0 deletions lib/http-invocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ HttpInvocation.prototype.createRequest = function () {
var accepts = method.accepts;
var ctorAccepts = null;
var returns = method.returns;
var errors = method.errors;
var query;
var i;

Expand Down Expand Up @@ -222,6 +223,7 @@ HttpInvocation.prototype.transformResponse = function(res, body, callback) {
var callbackArgs = [null]; // null => placeholder for err
var method = this.method;
var returns = method.returns;
var errors = method.errors;
var isObject = typeof body === 'object';
var err;
var hasError = res.statusCode >= 400;
Expand Down
4 changes: 3 additions & 1 deletion lib/jsonrpc-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,11 @@ JsonRpcAdapter.prototype.allRoutes = function () {
verb: verb,
path: path,
description: method.description,
notes: method.notes,
method: method.stringName,
accepts: (method.accepts && method.accepts.length) ? method.accepts : undefined,
returns: (method.returns && method.returns.length) ? method.returns : undefined
returns: (method.returns && method.returns.length) ? method.returns : undefined,
errors: (method.errors && method.errors.length) ? method.errors : undefined
});
}
};
Expand Down
3 changes: 2 additions & 1 deletion lib/remote-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ RemoteObjects.prototype.toJSON = function () {
result[sharedMethod.stringName] = {
http: sharedMethod.fn && sharedMethod.fn.http,
accepts: sharedMethod.accepts,
returns: sharedMethod.returns
returns: sharedMethod.returns,
errors: sharedMethod.errors
};
});

Expand Down
10 changes: 9 additions & 1 deletion lib/rest-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ RestAdapter.urlNotFoundHandler = function() {
RestAdapter.errorHandler = function(options) {
options = options || {};
return function restErrorHandler(err, req, res, next) {
if (options.handler) {
return options.handler(err, req, res, next)
}

if(typeof err === 'string') {
err = new Error(err);
err.status = err.statusCode = 500;
Expand Down Expand Up @@ -455,9 +459,11 @@ RestAdapter.prototype.allRoutes = function () {
verb: verb,
path: path,
description: method.description,
notes: method.notes,
method: method.stringName,
accepts: (method.accepts && method.accepts.length) ? method.accepts : undefined,
returns: (method.returns && method.returns.length) ? method.returns : undefined
returns: (method.returns && method.returns.length) ? method.returns : undefined,
errors: (method.errors && method.errors.length) ? method.errors : undefined
});
}
}
Expand Down Expand Up @@ -498,7 +504,9 @@ function RestMethod(restClass, sharedMethod) {

this.accepts = sharedMethod.accepts;
this.returns = sharedMethod.returns;
this.errors = sharedMethod.errors;
this.description = sharedMethod.description;
this.notes = sharedMethod.notes;

var methodRoutes = getRoutes(sharedMethod);
if (sharedMethod.isStatic || !restClass.ctor) {
Expand Down
1 change: 1 addition & 0 deletions lib/shared-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ function define(methods, name, options, fn) {
* define('myMethod', {
* accepts: {arg: 'str', type: 'string'},
* returns: {arg: 'str', type: 'string'}
* errors: [ { code: 404, message: 'Not Found', responseModel: 'Error' } ]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bajtos do you have any comments here?

I'm going to change responseModel to type unless anyone objects.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ritch - Please keep this Swagger 1.2 compliant, until 2.0 is stable and supported by the whole ecosystem

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ritch If I understand your intentions correctly, we will use type property in remoting metadata, and then convert it to responseModel property in strongloop/loopback-component-explorer#52. That way the strong-remoting part is not tied to Swagger, the DSL stays consistent, and we can support both Swagger 1.2 and 2.0 in the future.

If that's the case, then +1000.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ritch If I understand your intentions correctly, we will use type property in remoting metadata, and then convert it to responseModel property in strongloop/loopback-component-explorer#52. That way the strong-remoting part is not tied to Swagger, the DSL stays consistent, and we can support both Swagger 1.2 and 2.0 in the future.

exactly

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ritch - Please keep this Swagger 1.2 compliant, until 2.0 is stable and supported by the whole ecosystem

This should be possible in the conversion.

* }, myMethod);
* });
* function myMethod(str, cb) {
Expand Down
48 changes: 44 additions & 4 deletions lib/shared-method.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ var debug = require('debug')('strong-remoting:shared-method')
* @param {Object} [options.accepts.rest] The REST mapping / settings for the
* argument.
* @param {Array|Object} [options.returns] An `Array` of argument definitions
* @param {Array|Object} [options.errors] An `Array` of error definitions
* The same options are available as `options.accepts`.
* @property {String} name The method name
* @property {String[]} aliases An array of method aliases
* @property {Array|Object} isStatic
* @property {Array|Object} accepts See `options.accepts`
* @property {Array|Object} returns See `options.returns`
* @property {Array|Object} errors See `options.errors`
* @property {String} description
* @property {String} notes
* @property {String} http
* @property {String} rest
* @property {String} shared
Expand All @@ -70,7 +73,9 @@ function SharedMethod(fn, name, sc, options) {
var isStatic = this.isStatic = options.isStatic || false;
this.accepts = options.accepts || fn.accepts || [];
this.returns = options.returns || fn.returns || [];
this.errors = options.errors || fn.errors || [];
this.description = options.description || fn.description;
this.notes = options.notes || fn.notes;
this.http = options.http || fn.http || {};
this.rest = options.rest || fn.rest || {};
this.shared = options.shared;
Expand All @@ -96,6 +101,9 @@ function SharedMethod(fn, name, sc, options) {
if(this.returns && !Array.isArray(this.returns)) {
this.returns = [this.returns];
}
if(this.errors && !Array.isArray(this.errors)) {
this.errors = [this.errors];
}

this.stringName = (sc ? sc.name : '') + (isStatic ? '.' : '.prototype.') + name;
}
Expand All @@ -115,7 +123,9 @@ SharedMethod.fromFunction = function(fn, name, sharedClass, isStatic) {
isStatic: isStatic,
accepts: fn.accepts,
returns: fn.returns,
errors: fn.errors,
description: fn.description,
notes: fn.notes,
http: fn.http,
rest: fn.rest
});
Expand All @@ -131,6 +141,7 @@ SharedMethod.fromFunction = function(fn, name, sharedClass, isStatic) {
SharedMethod.prototype.invoke = function (scope, args, fn) {
var accepts = this.accepts;
var returns = this.returns;
var errors = this.errors;
var method = this.getFunction();
var sharedMethod = this;
var formattedArgs = [];
Expand Down Expand Up @@ -158,19 +169,48 @@ SharedMethod.prototype.invoke = function (scope, args, fn) {
}
}

if(actualType === 'number' && Number.isNaN(uarg)) {
var err = new Error(name + ' must be a number');
err.statusCode = 400;
return fn(err);
}

// convert strings
if(actualType === 'string' && desc.type !== 'any' && actualType !== desc.type) {
switch(desc.type) {
targetType = desc.type
if (Array.isArray(desc.type) && targetType.length == 1) {
uarg = uarg.split(/[,|]/)
targetType = targetType[0]
}
switch(targetType) {
case 'string':
break;
case 'date':
uarg = new Date(uarg);
if (Array.isArray(uarg)) {
for (i in uarg) {
uarg[i] = new Date(uarg[i]);
}
} else {
uarg = new Date(uarg);
}
break;
case 'number':
uarg = Number(uarg);
if (Array.isArray(uarg)) {
for (i in uarg) {
uarg[i] = Number(uarg[i]);
}
} else {
uarg = Number(uarg);
}
break;
case 'boolean':
uarg = Boolean(uarg);
if (Array.isArray(uarg)) {
for (i in uarg) {
uarg[i] = Boolean(uarg[i]);
}
} else {
uarg = Boolean(uarg);
}
break;
// Other types such as 'object', 'array',
// ModelClass, ['string'], or [ModelClass]
Expand Down
1 change: 1 addition & 0 deletions lib/socket-io-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ SocketIOContext.prototype.invoke = function (scope, method, fn) {
var args = method.isSharedCtor ? this.ctorArgs : this.args;
var accepts = method.accepts;
var returns = method.returns;
var errors = method.errors;
var scope;
var result;

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"request": "~2.42.0",
"browser-request": "~0.3.2",
"qs": "~2.2.3",
"inflection": "~1.4.2"
"inflection": "~1.4.2",
"js2xmlparser": "~0.1.4"
},
"devDependencies": {
"supertest": "~0.13.0",
Expand Down
10 changes: 10 additions & 0 deletions test/rest-adapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,21 @@ describe('RestAdapter', function() {
expect(method.returns).to.eql([anArg]);
});

it('has `errors`', function() {
var method = givenRestStaticMethod({ errors: anArg });
expect(method.errors).to.eql([anArg]);
});

it('has `description`', function() {
var method = givenRestStaticMethod({ description: 'a-desc' });
expect(method.description).to.equal('a-desc');
});

it('has `notes`', function() {
var method = givenRestStaticMethod({ notes: 'some-notes' });
expect(method.notes).to.equal('some-notes');
});

describe('isReturningArray()', function() {
it('returns true when there is single root Array arg', function() {
var method = givenRestStaticMethod({
Expand Down
Loading