feat: add compatiblity with Express's router middleware
This commit is contained in:
parent
c0c69246d2
commit
17188eb870
3 changed files with 189 additions and 2 deletions
15
README.md
15
README.md
|
@ -87,6 +87,21 @@ The _callback_ parameter has these arguments of the SVR.JS mod callback:
|
||||||
|
|
||||||
You can read more about the SVR.JS mod callbacks in the [SVR.JS mod API documentation](https://svrjs.org/docs/api/svrjs-api).
|
You can read more about the SVR.JS mod callbacks in the [SVR.JS mod API documentation](https://svrjs.org/docs/api/svrjs-api).
|
||||||
|
|
||||||
|
### _router.passExpressRouterMiddleware([path, ]middleware)_
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- _path_ - the path (begins with "/"), for which the route applies. (optional, _String_)
|
||||||
|
- _middleware_ - the middleware compatible with the `router` library (_Function_)
|
||||||
|
|
||||||
|
Returns: the SVRouter router (so that you can chain the methods for routes or pass-throughs)
|
||||||
|
|
||||||
|
The function adds middleware compatible with the `router` library to the SVRouter router.
|
||||||
|
|
||||||
|
The _middleware_ parameter has these arguments of middleware compatible with the `router` library:
|
||||||
|
- _req_ - the request object
|
||||||
|
- _res_ - the response object
|
||||||
|
- _next_ - the callback which passes the execution to other routes, SVR.JS mods and SVR.JS internal handlers.
|
||||||
|
|
||||||
### _router.get(path, callback)_
|
### _router.get(path, callback)_
|
||||||
An alias to the _router.route("GET", path, callback)_ function
|
An alias to the _router.route("GET", path, callback)_ function
|
||||||
|
|
||||||
|
|
33
src/index.js
33
src/index.js
|
@ -92,6 +92,38 @@ function svrouter() {
|
||||||
return router;
|
return router;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const passExpressRouterMiddleware = (path, middleware) => {
|
||||||
|
const realMiddleware = middleware ? middleware : path;
|
||||||
|
if (typeof realMiddleware !== "function") {
|
||||||
|
throw new Error("The passed middleware must be a function.");
|
||||||
|
} else if (middleware && typeof path !== "string") {
|
||||||
|
throw new Error("The path must be a string.");
|
||||||
|
}
|
||||||
|
const realPath = realMiddleware ? path.replace(/\/+$/, "") : "";
|
||||||
|
|
||||||
|
const callback = (req, res, logFacilities, config, next) => {
|
||||||
|
const previousReqBaseUrl = req.baseUrl;
|
||||||
|
const previousReqUrl = req.url;
|
||||||
|
const previousReqOriginalUrl = req.originalUrl;
|
||||||
|
|
||||||
|
req.baseUrl = realPath;
|
||||||
|
req.originalUrl = req.url;
|
||||||
|
req.url = req.url.substr(realPath.length); // Let's assume the request URL begins with the contents of realPath variable.
|
||||||
|
if (!req.url) req.url = "/";
|
||||||
|
|
||||||
|
const nextCallback = () => {
|
||||||
|
req.baseUrl = previousReqBaseUrl;
|
||||||
|
req.url = previousReqUrl;
|
||||||
|
req.originalUrl = previousReqOriginalUrl;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
realMiddleware(req, res, nextCallback);
|
||||||
|
};
|
||||||
|
|
||||||
|
return passRoute(realPath, callback);
|
||||||
|
};
|
||||||
|
|
||||||
const methods = http.METHODS
|
const methods = http.METHODS
|
||||||
? http.METHODS
|
? http.METHODS
|
||||||
: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
|
: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
|
||||||
|
@ -104,6 +136,7 @@ function svrouter() {
|
||||||
router.all = (path, callback) => addRoute("*", path, callback);
|
router.all = (path, callback) => addRoute("*", path, callback);
|
||||||
router.route = addRoute;
|
router.route = addRoute;
|
||||||
router.pass = passRoute;
|
router.pass = passRoute;
|
||||||
|
router.passExpressRouterMiddleware = passExpressRouterMiddleware;
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,7 @@ describe("SVRouter", () => {
|
||||||
expect(res.end).toHaveBeenCalledWith("Pass-through matched");
|
expect(res.end).toHaveBeenCalledWith("Pass-through matched");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should work with chained adding of routes and pass-throughs", () => {
|
test("should support chaining with adding of routes and pass-throughs", () => {
|
||||||
const req = {
|
const req = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
parsedURL: { pathname: "/anything" },
|
parsedURL: { pathname: "/anything" },
|
||||||
|
@ -201,6 +201,117 @@ describe("SVRouter", () => {
|
||||||
expect(passedThrough.config).toBe(config);
|
expect(passedThrough.config).toBe(config);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should handle middleware added with passExpressRouterMiddleware", () => {
|
||||||
|
const req = {
|
||||||
|
method: "GET",
|
||||||
|
parsedURL: { pathname: "/api/resource" },
|
||||||
|
url: "/resource",
|
||||||
|
originalUrl: "/api/resource",
|
||||||
|
baseUrl: "",
|
||||||
|
params: null
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
end: jest.fn()
|
||||||
|
};
|
||||||
|
const middleware = jest.fn((req, res, next) => {
|
||||||
|
res.end("Middleware matched");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.passExpressRouterMiddleware("/api", middleware);
|
||||||
|
|
||||||
|
router(req, res, null, null, () => {
|
||||||
|
res.end("No middleware matched");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(middleware).toHaveBeenCalled();
|
||||||
|
expect(res.end).toHaveBeenCalledWith("Middleware matched");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should restore req.url and req.baseUrl after middleware runs", () => {
|
||||||
|
const req = {
|
||||||
|
method: "GET",
|
||||||
|
parsedURL: { pathname: "/api/resource" },
|
||||||
|
url: "/api/resource",
|
||||||
|
baseUrl: null,
|
||||||
|
originalUrl: null,
|
||||||
|
params: null
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
end: jest.fn(),
|
||||||
|
error: jest.fn()
|
||||||
|
};
|
||||||
|
|
||||||
|
router.passExpressRouterMiddleware("/api", (req, res, next) => {
|
||||||
|
expect(req.baseUrl).toBe("/api");
|
||||||
|
expect(req.url).toBe("/resource");
|
||||||
|
expect(req.originalUrl).toBe("/api/resource");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router(req, res, null, null, () => {
|
||||||
|
expect(req.baseUrl).toBeNull();
|
||||||
|
expect(req.url).toBe("/api/resource");
|
||||||
|
expect(req.originalUrl).toBeNull();
|
||||||
|
res.end("Middleware chain completed");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.error).not.toHaveBeenCalled();
|
||||||
|
expect(res.end).toHaveBeenCalledWith("Middleware chain completed");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if no middleware matches in passExpressRouterMiddleware", () => {
|
||||||
|
const req = {
|
||||||
|
method: "GET",
|
||||||
|
parsedURL: { pathname: "/nomatch/resource" },
|
||||||
|
url: "/resource",
|
||||||
|
originalUrl: "/nomatch/resource",
|
||||||
|
baseUrl: "",
|
||||||
|
params: null
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
end: jest.fn()
|
||||||
|
};
|
||||||
|
const next = jest.fn();
|
||||||
|
|
||||||
|
router.passExpressRouterMiddleware("/api", (req, res, next) => {
|
||||||
|
res.end("Middleware matched");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router(req, res, null, null, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(res.end).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should support chaining with passExpressRouterMiddleware", () => {
|
||||||
|
const req = {
|
||||||
|
method: "GET",
|
||||||
|
parsedURL: { pathname: "/api/resource" },
|
||||||
|
url: "/resource",
|
||||||
|
originalUrl: "/api/resource",
|
||||||
|
baseUrl: "",
|
||||||
|
params: null
|
||||||
|
};
|
||||||
|
const res = {};
|
||||||
|
|
||||||
|
router
|
||||||
|
.passExpressRouterMiddleware("/api", (req, res, next) => {
|
||||||
|
res.firstMiddlewareRan = true;
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.passExpressRouterMiddleware("/api", (req, res, next) => {
|
||||||
|
res.secondMiddlewareRan = true;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router(req, res, null, null, () => {});
|
||||||
|
|
||||||
|
expect(res.firstMiddlewareRan).toBe(true);
|
||||||
|
expect(res.secondMiddlewareRan).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test("should throw an error if method is not a string in route", () => {
|
test("should throw an error if method is not a string in route", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
router.route(123, "/path", () => {});
|
router.route(123, "/path", () => {});
|
||||||
|
@ -213,12 +324,18 @@ describe("SVRouter", () => {
|
||||||
}).toThrow("The route callback must be a function.");
|
}).toThrow("The route callback must be a function.");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should throw an error if path is not a string in passRoute", () => {
|
test("should throw an error if path is not a string in pass", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
router.pass(123, () => {});
|
router.pass(123, () => {});
|
||||||
}).toThrow("The path must be a string.");
|
}).toThrow("The path must be a string.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should throw an error if path is not a string in passExpressRouterMiddleware", () => {
|
||||||
|
expect(() => {
|
||||||
|
router.passExpressRouterMiddleware(123, () => {});
|
||||||
|
}).toThrow("The path must be a string.");
|
||||||
|
});
|
||||||
|
|
||||||
test("should handle errors thrown in route callbacks gracefully", () => {
|
test("should handle errors thrown in route callbacks gracefully", () => {
|
||||||
const req = {
|
const req = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -237,4 +354,26 @@ describe("SVRouter", () => {
|
||||||
|
|
||||||
expect(res.error).toHaveBeenCalledWith(500, expect.any(Error));
|
expect(res.error).toHaveBeenCalledWith(500, expect.any(Error));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should correctly handle errors in middleware added with passExpressRouterMiddleware", () => {
|
||||||
|
const req = {
|
||||||
|
method: "GET",
|
||||||
|
parsedURL: { pathname: "/api/resource" },
|
||||||
|
url: "/resource",
|
||||||
|
originalUrl: "/api/resource",
|
||||||
|
baseUrl: "",
|
||||||
|
params: null
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
error: jest.fn()
|
||||||
|
};
|
||||||
|
|
||||||
|
router.passExpressRouterMiddleware("/api", () => {
|
||||||
|
throw new Error("Middleware error");
|
||||||
|
});
|
||||||
|
|
||||||
|
router(req, res, null, null, () => {});
|
||||||
|
|
||||||
|
expect(res.error).toHaveBeenCalledWith(500, expect.any(Error));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue