1
0
Fork 0

Merge branch 'devel' of https://github.com/triAGENS/ArangoDB into devel

This commit is contained in:
Jan Steemann 2014-09-16 22:09:09 +02:00
commit 988a622b54
8 changed files with 255 additions and 70 deletions

View File

@ -85,6 +85,29 @@ do the same for all routes of a controller. For this purpose
use the *allRoutes* object of the according controller.
The following methods are available.
*Examples*
Provide an error response for all routes handled by this controller:
```js
ctrl.allRoutes
.errorResponse(Unauthorized, 401, 'Not authenticated.')
.errorResponse(NotFound, 404, 'Document not found.')
.errorResponse(ImATeapot, 418, 'I\'m a teapot.');
ctrl.get('/some/route', function (req, res) {
// ...
throw new NotFound('The document does not exist');
// ...
}); // no errorResponse needed here
ctrl.get('/another/route', function (req, res) {
// ...
throw new NotFound('I made you a cookie but I ated it');
// ...
}); // no errorResponse needed here either
```
!SUBSECTION Buffer Error Response
<!-- js/server/modules/org/arangodb/foxx/request_context.js -->
@startDocuBlock JSF_foxx_RequestContextBuffer_errorResponse

View File

@ -191,7 +191,6 @@ function FILTER (list, examples) {
for (i = 0; i < list.length; ++i) {
var element = list[i];
if (MATCHES(element, examples, false)) {
result.push(element);
}
@ -3860,7 +3859,6 @@ function MATCHES (element, examples, returnIndex) {
if (! Array.isArray(examples)) {
examples = [ examples ];
}
if (examples.length === 0) {
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "MATCHES");
}
@ -3870,7 +3868,6 @@ function MATCHES (element, examples, returnIndex) {
for (i = 0; i < examples.length; ++i) {
var example = examples[i];
var result = true;
if (TYPEWEIGHT(example) !== TYPEWEIGHT_DOCUMENT) {
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "MATCHES");
}
@ -5997,6 +5994,11 @@ function GENERAL_GRAPH_NEIGHBORS (graphName,
options.startVertexCollectionRestriction = options.vertexCollectionRestriction;
}
}
if (options.neighborExamples) {
if (typeof options.neighborExamples === "string") {
options.neighborExamples = {_id : options.neighborExamples};
}
}
var neighbors = [],
params = TRAVERSAL_PARAMS(),
factory = TRAVERSAL.generalGraphDatasourceFactory(graphName);
@ -6005,14 +6007,12 @@ function GENERAL_GRAPH_NEIGHBORS (graphName,
params.paths = true;
params.visitor = TRAVERSAL_NEIGHBOR_VISITOR;
var fromVertices = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options);
if (options.edgeExamples) {
params.followEdges = options.edgeExamples;
}
if (options.edgeCollectionRestriction) {
params.edgeCollectionRestriction = options.edgeCollectionRestriction;
}
fromVertices.forEach(function (v) {
var e = TRAVERSAL_FUNC("GRAPH_NEIGHBORS",
factory,

View File

@ -346,7 +346,11 @@ extend(Controller.prototype, {
/// The before function takes a *path* on which it should watch and a
/// function that it should execute before the routing takes place. If you do
/// omit the path, the function will be executed before each request, no matter
/// the path. Your function gets a Request and a Response object.
/// the path. Your function gets a Request and a Response object.
///
/// If your callback returns the Boolean value *false*, the route handling
/// will not proceed. You can use this to intercept invalid or unauthorized
/// requests and prevent them from being passed to the matching routes.
///
/// @EXAMPLES
///
@ -371,8 +375,10 @@ extend(Controller.prototype, {
url: {match: path},
action: {
callback: function (req, res, opts, next) {
func(req, res, opts);
next();
var result = func(req, res, opts);
if (result !== false) {
next();
}
}
}
});

View File

@ -192,13 +192,14 @@ function extendContext (context, app, root) {
"use strict";
var cp = context.collectionPrefix;
var cname = "";
if (cp !== "" && cp !== "_") {
cp += "_";
if (cp !== "") {
cname = cp + "_";
}
context.collectionName = function (name) {
var replaced = ((cp + name).replace(/[^a-zA-Z0-9]/g, '_').replace(/(^_+|_+$)/g, '')).substr(0, 64);
var replaced = (cname + name).replace(/[^a-zA-Z0-9]/g, '_').replace(/(^_+|_+$)/g, '').substr(0, 64);
if (replaced.length === 0) {
throw new Error("Cannot derive collection name from '" + name + "'");

View File

@ -28,6 +28,7 @@
////////////////////////////////////////////////////////////////////////////////
var Repository,
Model = require("org/arangodb/foxx/model").Model,
_ = require("underscore"),
extend = require('org/arangodb/extend').extend;
@ -82,7 +83,7 @@ Repository = function (collection, opts) {
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////
this.modelPrototype = this.options.model || require("org/arangodb/foxx/model").Model;
this.modelPrototype = this.options.model || Model;
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_repository_prefix
@ -262,7 +263,7 @@ _.extend(Repository.prototype, {
remove: function (model) {
'use strict';
var id = model.get('_id');
this.collection.remove(id);
return this.collection.remove(id);
},
////////////////////////////////////////////////////////////////////////////////
@ -281,7 +282,7 @@ _.extend(Repository.prototype, {
////////////////////////////////////////////////////////////////////////////////
removeById: function (id) {
'use strict';
this.collection.remove(id);
return this.collection.remove(id);
},
////////////////////////////////////////////////////////////////////////////////
@ -299,7 +300,7 @@ _.extend(Repository.prototype, {
////////////////////////////////////////////////////////////////////////////////
removeByExample: function (example) {
'use strict';
this.collection.removeByExample(example);
return this.collection.removeByExample(example);
},
// -----------------------------------------------------------------------------
@ -311,7 +312,7 @@ _.extend(Repository.prototype, {
/// `FoxxRepository#replace(model)`
///
/// Find the model in the database by its *_id* and replace it with this version.
/// Expects a model. Sets the Revision of the model.
/// Expects a model. Sets the revision of the model.
/// Returns the model.
///
/// @EXAMPLES
@ -324,7 +325,7 @@ _.extend(Repository.prototype, {
////////////////////////////////////////////////////////////////////////////////
replace: function (model) {
'use strict';
var id = model.get("_id"),
var id = model.get("_id") || model.get("_key"),
data = model.forDB(),
id_and_rev = this.collection.replace(id, data);
model.set(id_and_rev);
@ -333,11 +334,12 @@ _.extend(Repository.prototype, {
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_repository_replaceById
/// `FoxxRepository#replaceById(id, model)`
/// `FoxxRepository#replaceById(id, object)`
///
/// Find the model in the database by the given ID and replace it with the given.
/// model.
/// Sets the ID and Revision of the model and also returns it.
/// Find the item in the database by the given ID and replace it with the
/// given object's attributes.
///
/// If the object is a model, updates the model's revision and returns the model.
///
/// @EXAMPLES
///
@ -346,21 +348,22 @@ _.extend(Repository.prototype, {
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////
replaceById: function (id, model) {
replaceById: function (id, data) {
'use strict';
var data = model.forDB(),
id_and_rev = this.collection.replace(id, data);
model.set(id_and_rev);
return model;
if (data instanceof Model) {
var id_and_rev = this.collection.replace(id, data.forDB());
data.set(id_and_rev);
return data;
}
return this.collection.replace(id, data);
},
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_repository_replaceByExample
/// `FoxxRepository#replaceByExample(example, model)`
/// `FoxxRepository#replaceByExample(example, object)`
///
/// Find the model in the database by the given example and replace it with the given.
/// model.
/// Sets the ID and Revision of the model and also returns it.
/// Find every matching item by example and replace it with the attributes in
/// the provided object.
///
/// @EXAMPLES
///
@ -369,24 +372,46 @@ _.extend(Repository.prototype, {
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////
replaceByExample: function (example, model) {
replaceByExample: function (example, data) {
'use strict';
var data = model.forDB(),
idAndRev = this.collection.replaceByExample(example, data);
model.set(idAndRev);
return model;
return this.collection.replaceByExample(example, data);
},
// -----------------------------------------------------------------------------
// --SUBSECTION-- Updating Entries
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_repository_update
/// `FoxxRepository#update(model, object)`
///
/// Find the model in the database by its *_id* and update it with the given object.
/// Expects a model. Sets the revision of the model and updates its properties.
/// Returns the model.
///
/// @EXAMPLES
///
/// ```javascript
/// repository.update(myModel, {name: 'Jan Steeman'});
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////
update: function (model, data) {
'use strict';
var id = model.get("_id") || model.get("_key"),
id_and_rev = this.collection.update(id, data);
model.set(data);
model.set(id_and_rev);
return model;
},
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_repository_updateById
/// `FoxxRepository#updateById(id, object)`
///
/// Find an item by ID and update it with the attributes in the provided object.
/// Returns the updated model.
///
/// If the object is a model, updates the model's revision and returns the model.
///
/// @EXAMPLES
///
@ -395,17 +420,22 @@ _.extend(Repository.prototype, {
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////
updateById: function (id, updates) {
updateById: function (id, data) {
'use strict';
this.collection.update(id, updates);
if (data instanceof Model) {
var id_and_rev = this.collection.update(id, data.forDB());
data.set(id_and_rev);
return data;
}
return this.collection.update(id, data);
},
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_repository_updateByExample
/// `FoxxRepository#updateByExample(example, object)`
///
/// Find an item by example and update it with the attributes in the provided object.
/// Returns the updated model.
/// Find every matching item by example and update it with the attributes in
/// the provided object.
///
/// @EXAMPLES
///
@ -414,9 +444,9 @@ _.extend(Repository.prototype, {
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////
updateByExample: function (example, updates) {
updateByExample: function (example, data) {
'use strict';
this.collection.updateByExample(example, updates);
return this.collection.updateByExample(example, data);
},
// -----------------------------------------------------------------------------

View File

@ -220,13 +220,23 @@ extend(RequestContext.prototype, {
///
/// You can also provide a description of this parameter.
///
/// @EXAMPLES
/// *Examples*
///
/// ```js
/// app.get("/foxx/:id", function {
/// // Do something
/// }).pathParam("id", type: joi.number().integer().required().description("Id of the Foxx"));
/// ```
///
/// You can also pass in a configuration object instead:
///
/// ```js
/// app.get("/foxx/:id", function {
/// // Do something
/// }).pathParam("id", {
/// type: joi.number().integer().required().description("Id of the Foxx")
/// type: joi.number().integer(),
/// required: true,
/// description: "Id of the Foxx"
/// });
/// ```
/// @endDocuBlock
@ -239,9 +249,16 @@ extend(RequestContext.prototype, {
type = attributes.type,
required = attributes.required,
description = attributes.description,
constraint = type,
regexType = type,
cfg;
constraint, regexType, cfg;
if (attributes.isJoi) {
type = attributes;
required = undefined;
description = undefined;
}
constraint = type;
regexType = type;
// deprecated: assume type.describe is always a function
if (type && typeof type.describe === 'function') {
@ -306,6 +323,19 @@ extend(RequestContext.prototype, {
/// ```js
/// app.get("/foxx", function {
/// // Do something
/// }).queryParam("id",
/// joi.number().integer()
/// .required()
/// .description("Id of the Foxx")
/// .meta({allowMultiple: false})
/// });
/// ```
///
/// You can also pass in a configuration object instead:
///
/// ```js
/// app.get("/foxx", function {
/// // Do something
/// }).queryParam("id", {
/// type: joi.number().integer().required().description("Id of the Foxx"),
/// allowMultiple: false
@ -319,8 +349,17 @@ extend(RequestContext.prototype, {
var type = attributes.type,
required = attributes.required,
description = attributes.description,
constraint = type,
cfg;
allowMultiple = attributes.allowMultiple,
constraint, cfg;
if (attributes.isJoi) {
type = attributes;
required = undefined;
description = undefined;
allowMultiple = undefined;
}
constraint = type;
// deprecated: assume type.describe is always a function
if (type && typeof type.describe === 'function') {
@ -330,6 +369,9 @@ extend(RequestContext.prototype, {
if (typeof description === 'string') {
constraint = constraint.description(description);
}
if (typeof allowMultiple === 'boolean') {
constraint = constraint.meta({allowMultiple: allowMultiple});
}
this.constraints.queryParams[paramName] = constraint;
cfg = constraint.describe();
if (Array.isArray(cfg)) {
@ -338,8 +380,18 @@ extend(RequestContext.prototype, {
} else {
type = cfg.type;
}
required = Boolean(cfg.flags && cfg.flags.presense === 'required');
required = Boolean(cfg.flags && cfg.flags.presence === 'required');
description = cfg.description;
if (cfg.meta) {
if (!Array.isArray(cfg.meta)) {
cfg.meta = [cfg.meta];
}
_.each(cfg.meta, function (meta) {
if (meta && typeof meta.allowMultiple === 'boolean') {
allowMultiple = meta.allowMultiple;
}
});
}
if (
type === 'number' &&
_.isArray(cfg.rules) &&
@ -356,7 +408,7 @@ extend(RequestContext.prototype, {
description,
type,
required,
attributes.allowMultiple
Boolean(allowMultiple)
);
return this;
},

View File

@ -288,25 +288,39 @@ describe('Repository Methods', function () {
});
it('should replace by example', function () {
var model = new Model({}),
idAndRev = createSpy('idAndRev'),
example = createSpy('example'),
data = createSpy('data'),
result;
var example = createSpy('example'),
data = createSpy('data');
spyOn(model, 'forDB').and.returnValue(data);
spyOn(model, 'set');
collection.replaceByExample.and.returnValue(idAndRev);
instance.replaceByExample(example, data);
result = instance.replaceByExample(example, model);
expect(result).toBe(model);
expect(model.set.calls.argsFor(0)).toEqual([idAndRev]);
expect(collection.replaceByExample.calls.argsFor(0)).toEqual([example, data]);
});
});
describe('for updating entries', function () {
it('should allow to update by model', function () {
var model = new Model({}),
idAndRev = createSpy('idAndRev'),
id = createSpy('id'),
data = createSpy('data'),
updates = createSpy('updates'),
result;
spyOn(model, 'get').and.returnValue(id);
spyOn(model, 'forDB').and.returnValue(data);
spyOn(model, 'set');
collection.update.and.returnValue(idAndRev);
result = instance.update(model, updates);
expect(result).toBe(model);
expect(model.set.calls.allArgs().length).toEqual(2);
expect(model.set.calls.allArgs()).toContain([idAndRev]);
expect(model.set.calls.allArgs()).toContain([updates]);
expect(collection.update.calls.argsFor(0)).toEqual([id, updates]);
expect(model.get.calls.argsFor(0)).toEqual(['_id']);
});
it('should update by id', function () {
var id = createSpy('id'),
updates = createSpy('updates'),
@ -320,13 +334,11 @@ describe('Repository Methods', function () {
it('should update by example', function () {
var example = createSpy('example'),
updates = createSpy('updates'),
idAndRev = createSpy('idAndRev');
data = createSpy('data');
collection.updateByExample.and.returnValue(idAndRev);
instance.updateByExample(example, updates);
instance.updateByExample(example, data);
expect(collection.updateByExample.calls.argsFor(0)).toEqual([example, updates]);
expect(collection.updateByExample.calls.argsFor(0)).toEqual([example, data]);
});
});

View File

@ -514,6 +514,21 @@ function DocumentationAndConstraintsSpec () {
assertEqual(context.constraints.urlParams, {id: constraint});
},
testDefinePathParamShorthand: function () {
var constraint = joi.number().integer().description("Id of the Foxx"),
context = app.get('/foxx/:id', function () {
//nothing
}).pathParam("id", constraint);
assertEqual(routes.length, 1);
assertEqual(routes[0].url.constraint.id, "/[0-9]+/");
assertEqual(routes[0].docs.parameters[0].paramType, "path");
assertEqual(routes[0].docs.parameters[0].name, "id");
assertEqual(routes[0].docs.parameters[0].description, "Id of the Foxx");
assertEqual(routes[0].docs.parameters[0].dataType, "integer");
assertEqual(context.constraints.urlParams, {id: constraint});
},
testDefinePathCaseParam: function () {
var constraint = joi.number().integer().description("Id of the Foxx"),
context = app.get('/foxx/:idParam', function () {
@ -592,10 +607,56 @@ function DocumentationAndConstraintsSpec () {
context = app.get('/foxx', function () {
//nothing
}).queryParam("a", {
type: constraint,
allowMultiple: true
type: constraint
});
assertEqual(routes.length, 1);
assertEqual(routes[0].docs.parameters[0].paramType, "query");
assertEqual(routes[0].docs.parameters[0].name, "a");
assertEqual(routes[0].docs.parameters[0].description, "The value of an a");
assertEqual(routes[0].docs.parameters[0].dataType, "integer");
assertEqual(routes[0].docs.parameters[0].required, false);
assertEqual(routes[0].docs.parameters[0].allowMultiple, false);
assertEqual(context.constraints.queryParams, {a: constraint});
},
testDefineQueryParamWithOverrides: function () {
var constraint = joi.number().integer(),
context = app.get('/foxx', function () {
//nothing
}).queryParam("a", {
type: constraint,
description: "The value of an a",
allowMultiple: true,
required: true
});
assertEqual(routes.length, 1);
assertEqual(routes[0].docs.parameters[0].paramType, "query");
assertEqual(routes[0].docs.parameters[0].name, "a");
assertEqual(routes[0].docs.parameters[0].description, "The value of an a");
assertEqual(routes[0].docs.parameters[0].dataType, "integer");
print(0)
assertEqual(routes[0].docs.parameters[0].required, true);
print(1)
assertEqual(routes[0].docs.parameters[0].allowMultiple, true);
print(2)
assertEqual(context.constraints.queryParams, {
a: constraint
.description("The value of an a")
.meta({allowMultiple: true})
.required()
});
},
testDefineQueryParamShorthand: function () {
var constraint = joi.number().integer()
.description("The value of an a")
.meta({allowMultiple: true}),
context = app.get('/foxx', function () {
//nothing
}).queryParam("a", constraint);
assertEqual(routes.length, 1);
assertEqual(routes[0].docs.parameters[0].paramType, "query");
assertEqual(routes[0].docs.parameters[0].name, "a");