Skip to content

Commit 5d05252

Browse files
committed
feat: allow exclude prefix set dynamaic route
1 parent 33f32b4 commit 5d05252

3 files changed

Lines changed: 118 additions & 64 deletions

File tree

examples/such.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = {
1111
cors: true,
1212
prefix: [prefix, {
1313
exclude: [{
14-
path: /list(\/\d+)?/,
14+
path: "/list/:id?",
1515
method: 'post'
1616
}]
1717
}],

lib/commands/such-serve.js

Lines changed: 116 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ program
165165
}
166166
}
167167
};
168+
// dynamic route rule
169+
const dynamicRouteRule = /\/:/;
168170
// http server
169171
const startHttpServer = async () => {
170172
const suchStoreConfig = cliSuch.store("config");
@@ -217,22 +219,118 @@ program
217219
? () => timeout[0] + Math.round(Math.random() * timeout[1])
218220
: () => timeout || 0;
219221
const genTimeout = genTimeoutHandle(timeout);
222+
// dynamic helper handles
223+
const getDynamicRoutePatterns = (pathname) => {
224+
const segments = trimPathnameLeft(pathname).split("/");
225+
const pathSegs = [];
226+
const patterns = [];
227+
for (const seg of segments) {
228+
if (seg.startsWith(":")) {
229+
let len = seg.length;
230+
let optional = false;
231+
if (seg.endsWith("?")) {
232+
optional = true;
233+
len--;
234+
}
235+
const param = seg.slice(1, len);
236+
patterns.push({
237+
param,
238+
optional,
239+
});
240+
pathSegs.push(`_${param}`);
241+
} else {
242+
patterns.push({
243+
path: seg,
244+
optional: false,
245+
});
246+
pathSegs.push(seg);
247+
}
248+
}
249+
return {
250+
pathSegs,
251+
patterns,
252+
};
253+
};
254+
const buildDynamicMatchFn = (pathname, patterns) => {
255+
if (dynamicMatchFns[pathname]) {
256+
return dynamicMatchFns[pathname];
257+
}
258+
if (!patterns) {
259+
patterns = getDynamicRoutePatterns(pathname).patterns;
260+
}
261+
return (dynamicMatchFns[pathname] = (segs) => {
262+
const params = {};
263+
for (const [index, seg] of Object.entries(segs)) {
264+
const pattern = patterns[index] || {};
265+
if (hasOwn(pattern, "path")) {
266+
if (pattern.path === seg) {
267+
continue;
268+
}
269+
} else if (hasOwn(pattern, "param")) {
270+
params[pattern.param] = seg;
271+
continue;
272+
}
273+
return {
274+
matched: false,
275+
params,
276+
};
277+
}
278+
if (patterns.length > segs.length) {
279+
const leavePatterns = patterns.slice(segs.length);
280+
for (const pattern of leavePatterns) {
281+
if (!pattern.optional) {
282+
return {
283+
matched: false,
284+
params,
285+
};
286+
}
287+
}
288+
}
289+
return {
290+
matched: true,
291+
params,
292+
};
293+
});
294+
};
295+
const dynamicMatchFns = {};
296+
// do with the prefix
220297
const hasPrefixExclude = Array.isArray(prefix);
221298
// find and remove the prefix
222299
const matchPrefix = prefix
223300
? hasPrefixExclude
224301
? (pathname, method) => {
225302
const [curPrefix, { exclude = [] } = {}] = prefix;
303+
let params = {};
226304
// check if is in exclude
227305
const isInExclude = exclude.some((item) => {
306+
let isDynamic = false;
307+
let excludePathname;
228308
if (typeof item === "string") {
229-
return pathname === item;
309+
if (dynamicRouteRule.test(item)) {
310+
isDynamic = true;
311+
excludePathname = item;
312+
} else {
313+
return pathname === item;
314+
}
230315
} else if (isObject(item) && item.path) {
231316
let isMatch = false;
232-
if (typeof item.path === "string") {
233-
isMatch = item.path === pathname;
234-
} else if (item.path instanceof RegExp) {
235-
isMatch = item.path.test(pathname);
317+
const curPath = item.path;
318+
if (typeof curPath === "string") {
319+
if (dynamicRouteRule.test(curPath)) {
320+
isDynamic = true;
321+
excludePathname = curPath;
322+
} else {
323+
isMatch = curPath === pathname;
324+
}
325+
} else if (curPath instanceof RegExp) {
326+
isMatch = curPath.test(pathname);
327+
}
328+
// dynamic route
329+
if (isDynamic) {
330+
const matchFn = buildDynamicMatchFn(excludePathname);
331+
const ret = matchFn(trimPathnameLeft(pathname).split("/"));
332+
isMatch = ret.matched;
333+
if (isMatch) params = ret.params;
236334
}
237335
if (isMatch && item.method) {
238336
return item.method === method;
@@ -246,6 +344,7 @@ program
246344
matched: true,
247345
pathname,
248346
exclude: true,
347+
params,
249348
};
250349
}
251350
// not in exclude
@@ -381,35 +480,12 @@ program
381480
? config.method
382481
: [config.method]
383482
: undefined;
384-
if (/\/:/.test(key)) {
385-
const segments = trimPathnameLeft(key).split("/");
386-
const pathSegs = [];
387-
const patterns = [];
388-
for (const seg of segments) {
389-
if (seg.startsWith(":")) {
390-
let len = seg.length;
391-
let optional = false;
392-
if (seg.endsWith("?")) {
393-
optional = true;
394-
len--;
395-
}
396-
const param = seg.slice(1, len);
397-
patterns.push({
398-
param,
399-
optional,
400-
});
401-
pathSegs.push(`_${param}`);
402-
} else {
403-
patterns.push({
404-
path: seg,
405-
optional: false,
406-
});
407-
pathSegs.push(seg);
408-
}
409-
}
483+
if (dynamicRouteRule.test(key)) {
484+
const { pathSegs, patterns } = getDynamicRoutePatterns(key);
410485
const pathname = hasOwn(config, "rewrite")
411486
? trimPathnameLeft(config.rewrite)
412487
: pathSegs.join("/");
488+
const matchFn = buildDynamicMatchFn(key, patterns);
413489
const matcher = (segs, method) => {
414490
// first, check the method if is matched
415491
if (allowMethods && !allowMethods.includes(method)) {
@@ -418,39 +494,16 @@ program
418494
};
419495
}
420496
// check the pathname segments
421-
const params = {};
422-
for (const [index, seg] of Object.entries(segs)) {
423-
const pattern = patterns[index] || {};
424-
if (hasOwn(pattern, "path")) {
425-
if (pattern.path === seg) {
426-
continue;
427-
}
428-
} else if (hasOwn(pattern, "param")) {
429-
params[pattern.param] = seg;
430-
continue;
431-
}
497+
const ret = matchFn(segs);
498+
if (ret.matched) {
432499
return {
433-
matched: false,
434-
params,
500+
matched: true,
501+
params: ret.params,
502+
pathname,
503+
origPathname: key,
435504
};
436505
}
437-
if (patterns.length > segs.length) {
438-
const leavePatterns = patterns.slice(segs.length);
439-
for (const pattern of leavePatterns) {
440-
if (!pattern.optional) {
441-
return {
442-
matched: false,
443-
params,
444-
};
445-
}
446-
}
447-
}
448-
return {
449-
matched: true,
450-
params,
451-
pathname,
452-
origPathname: key,
453-
};
506+
return ret;
454507
};
455508
dynamicRouteMatcher.push(matcher);
456509
} else {
@@ -518,6 +571,7 @@ program
518571
let matched = true;
519572
let searchExtensions = extensions;
520573
let reason = "";
574+
let params = {};
521575
printDebugInfo("Request url", addr);
522576
// print the debug information
523577
if (debug) printDebugInfo("The request's pathname", pathname);
@@ -552,6 +606,7 @@ program
552606
const prefixData = matchPrefix(pathname, method);
553607
matched = prefixData.matched;
554608
pathname = prefixData.pathname;
609+
params = prefixData.params || params;
555610
if (!matched)
556611
reason = `The url's pathname not match a prefix ${
557612
hasPrefixExclude
@@ -586,7 +641,6 @@ program
586641
// do with dynamic routes
587642
const pathSegs = pathname.split("/");
588643
const dynamicRoute = matchDynamicRoute(pathSegs, method);
589-
let params = {};
590644
if (dynamicRoute.matched) {
591645
pathname = dynamicRoute.pathname;
592646
params = dynamicRoute.params;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "such-cli",
33
"description": "Run a mock server & Generate fake data & Initialize suchjs in command line.",
4-
"version": "0.4.2",
4+
"version": "0.4.3",
55
"author": "[email protected]",
66
"bin": {
77
"such": "bin/such.js"

0 commit comments

Comments
 (0)