forked from meteor/validated-method
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidated-method.js
114 lines (92 loc) · 3.29 KB
/
validated-method.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/* global ValidatedMethod:true */
ValidatedMethod = class ValidatedMethod {
constructor(options) {
// Default to no mixins
options.mixins = options.mixins || [];
check(options.mixins, [Function]);
check(options.name, String);
options = applyMixins(options, options.mixins);
// connection argument defaults to Meteor, which is where Methods are defined on client and
// server
options.connection = options.connection || Meteor;
// Allow validate: null shorthand for methods that take no arguments
if (options.validate === null) {
options.validate = function () {};
}
check(options, Match.ObjectIncluding({
name: String,
validate: Function,
run: Function,
mixins: [Function],
connection: Object,
}));
_.extend(this, options);
const method = this;
this.connection.methods({
[options.name](args) {
// Silence audit-argument-checks since arguments are always checked when using this package
check(args, Match.Any);
const methodInvocation = this;
return method._execute(methodInvocation, args);
}
});
}
call(args, callback) {
// Accept calling with just a callback
if (_.isFunction(args)) {
callback = args;
args = {};
}
const options = {
// Make it possible to get the ID of an inserted item
returnStubValue: true,
// Don't call the server method if the client stub throws an error, so that we don't end
// up doing validations twice
// XXX needs option to disable, in cases where the client might have incomplete information to
// make a decision
throwStubExceptions: true
};
try {
return this.connection.apply(this.name, [args], options, callback);
} catch (err) {
if (callback) {
// Get errors from the stub in the same way as from the server-side method
callback(err);
} else {
// No callback passed, throw instead of silently failing; this is what
// "normal" Methods do if you don't pass a callback.
throw err;
}
}
}
_execute(methodInvocation, args) {
methodInvocation = methodInvocation || {};
// Add `this.name` to reference the Method name
methodInvocation.name = this.name;
const validateResult = this.validate.bind(methodInvocation)(args);
if (typeof validateResult !== 'undefined') {
throw new Error(`Returning from validate doesn't do anything; \
perhaps you meant to throw an error?`);
}
return this.run.bind(methodInvocation)(args);
}
};
// Mixins get a chance to transform the arguments before they are passed to the actual Method
function applyMixins(args, mixins) {
// You can pass nested arrays so that people can ship mixin packs
const flatMixins = _.flatten(mixins);
// Save name of the method here, so we can attach it to potential error messages
const {name} = args;
flatMixins.forEach((mixin) => {
args = mixin(args);
if(!Match.test(args, Object)) {
const functionName = mixin.toString().match(/function\s(\w+)/);
let msg = 'One of the mixins';
if(functionName) {
msg = `The function '${functionName[1]}'`;
}
throw new Error(`Error in ${name} method: ${msg} didn't return the options object.`);
}
});
return args;
}