forked from parse-community/parse-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PromiseRouter.js
148 lines (136 loc) · 4.42 KB
/
PromiseRouter.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// A router that is based on promises rather than req/res/next.
// This is intended to replace the use of express.Router to handle
// subsections of the API surface.
// This will make it easier to have methods like 'batch' that
// themselves use our routing information, without disturbing express
// components that external developers may be modifying.
function PromiseRouter() {
// Each entry should be an object with:
// path: the path to route, in express format
// method: the HTTP method that this route handles.
// Must be one of: POST, GET, PUT, DELETE
// handler: a function that takes request, and returns a promise.
// Successful handlers should resolve to an object with fields:
// status: optional. the http status code. defaults to 200
// response: a json object with the content of the response
// location: optional. a location header
this.routes = [];
}
// Global flag. Set this to true to log every request and response.
PromiseRouter.verbose = process.env.VERBOSE || false;
// Merge the routes into this one
PromiseRouter.prototype.merge = function(router) {
for (var route of router.routes) {
this.routes.push(route);
}
};
PromiseRouter.prototype.route = function(method, path, handler) {
switch(method) {
case 'POST':
case 'GET':
case 'PUT':
case 'DELETE':
break;
default:
throw 'cannot route method: ' + method;
}
this.routes.push({
path: path,
method: method,
handler: handler
});
};
// Returns an object with:
// handler: the handler that should deal with this request
// params: any :-params that got parsed from the path
// Returns undefined if there is no match.
PromiseRouter.prototype.match = function(method, path) {
for (var route of this.routes) {
if (route.method != method) {
continue;
}
// NOTE: we can only route the specific wildcards :className and
// :objectId, and in that order.
// This is pretty hacky but I don't want to rebuild the entire
// express route matcher. Maybe there's a way to reuse its logic.
var pattern = '^' + route.path + '$';
pattern = pattern.replace(':className',
'(_?[A-Za-z][A-Za-z_0-9]*)');
pattern = pattern.replace(':objectId',
'([A-Za-z0-9]+)');
var re = new RegExp(pattern);
var m = path.match(re);
if (!m) {
continue;
}
var params = {};
if (m[1]) {
params.className = m[1];
}
if (m[2]) {
params.objectId = m[2];
}
return {params: params, handler: route.handler};
}
};
// A helper function to make an express handler out of a a promise
// handler.
// Express handlers should never throw; if a promise handler throws we
// just treat it like it resolved to an error.
function makeExpressHandler(promiseHandler) {
return function(req, res, next) {
try {
if (PromiseRouter.verbose) {
console.log(req.method, req.originalUrl, req.headers,
JSON.stringify(req.body, null, 2));
}
promiseHandler(req).then((result) => {
if (!result.response) {
console.log('BUG: the handler did not include a "response" field');
throw 'control should not get here';
}
if (PromiseRouter.verbose) {
console.log('response:', JSON.stringify(result.response, null, 2));
}
var status = result.status || 200;
res.status(status);
if (result.location) {
res.set('Location', result.location);
}
res.json(result.response);
}, (e) => {
if (PromiseRouter.verbose) {
console.log('error:', e);
}
next(e);
});
} catch (e) {
if (PromiseRouter.verbose) {
console.log('error:', e);
}
next(e);
}
}
}
// Mount the routes on this router onto an express app (or express router)
PromiseRouter.prototype.mountOnto = function(expressApp) {
for (var route of this.routes) {
switch(route.method) {
case 'POST':
expressApp.post(route.path, makeExpressHandler(route.handler));
break;
case 'GET':
expressApp.get(route.path, makeExpressHandler(route.handler));
break;
case 'PUT':
expressApp.put(route.path, makeExpressHandler(route.handler));
break;
case 'DELETE':
expressApp.delete(route.path, makeExpressHandler(route.handler));
break;
default:
throw 'unexpected code branch';
}
}
};
module.exports = PromiseRouter;