-
Notifications
You must be signed in to change notification settings - Fork 3
/
handler.js
120 lines (101 loc) · 3.27 KB
/
handler.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
115
116
117
118
119
120
const fnArgs = require('function-arguments');
const errors = require('./errors');
function normalize(method) {
if (typeof method === 'function') {
return {
handler: method,
};
}
return method;
}
function getMatchFn(methodSchema) {
// Get string names of arguments defined in the handler function
const functionArgs = fnArgs(methodSchema.handler);
// Check whether arguments were also defined in the method schema
// If so, first validate that schema and function have same arity
if (methodSchema.args !== undefined) {
if (methodSchema.args.length !== functionArgs.length) {
throw errors.INVALID_ARG_LIST;
}
}
// Use the args from the method schema if defined or else from function
const schemaArgs = methodSchema.args || functionArgs;
// Now create an array (ordered) of arguments and their metadata
const argsMetadata = schemaArgs.map((arg, idx) => {
if (typeof arg === 'string') {
// If `arg` is a string, we use that for the argument
// `name` and assign default values to all the metadata
return {
name: arg,
optional: false,
};
}
// If `arg` is an object, it should have metadata from the schema
// `arg.name` is optional, so use name from method handler
// All the rest of the metadata should come from the schema
const name = arg.name || functionArgs[idx];
// Make sure the default value passes validation
if (arg.validate !== undefined && arg.default !== undefined) {
try {
arg.validate(arg.default);
} catch (err) {
throw errors.INVALID_DEFAULT;
}
}
return {
name,
default: arg.default,
optional: !!arg.optional,
parse: arg.parse,
validate: arg.validate,
};
});
// Finally cache list of the expected argument key strings/names
const expectedArgNames = argsMetadata.map(info => info.name);
// When invoked, we have to verify that:
// 1. verify that all parameters passed are recognized
// 2. verify that all required parameters were passed
// 3. replace missing, optional params with defaults
// 4. parse and validate any args with methods defined
// 5. produce ordered array of values to be used with apply
function matcher(args) {
Object.keys(args).forEach((arg) => {
if (!expectedArgNames.includes(arg)) {
throw errors.INVALID_ARG_NAME;
}
});
const values = argsMetadata.map((arg) => {
let passed = args[arg.name];
if (passed === undefined) {
if (!arg.optional) {
throw errors.MISSING_ARG;
}
passed = arg.default;
}
const parsedArg = arg.parse ? arg.parse(passed) : passed;
if (arg.validate !== undefined) {
arg.validate(parsedArg);
}
return parsedArg;
});
return values;
}
return matcher;
}
function handler(methodSchema) {
const normalizedSchema = normalize(methodSchema);
const match = getMatchFn(normalizedSchema);
function matchParams(koaCtx, params) {
const args = match(params);
koaCtx.swatchCtx.args = args;
}
function handleMethod(koaCtx) {
const args = koaCtx.swatchCtx.args;
return normalizedSchema.handler.apply(null, args);
}
return {
match: matchParams,
handle: handleMethod,
};
}
module.exports = handler;