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).
|
||||
|
||||
### _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)_
|
||||
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;
|
||||
};
|
||||
|
||||
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
|
||||
? http.METHODS
|
||||
: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
|
||||
|
@ -104,6 +136,7 @@ function svrouter() {
|
|||
router.all = (path, callback) => addRoute("*", path, callback);
|
||||
router.route = addRoute;
|
||||
router.pass = passRoute;
|
||||
router.passExpressRouterMiddleware = passExpressRouterMiddleware;
|
||||
|
||||
return router;
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ describe("SVRouter", () => {
|
|||
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 = {
|
||||
method: "GET",
|
||||
parsedURL: { pathname: "/anything" },
|
||||
|
@ -201,6 +201,117 @@ describe("SVRouter", () => {
|
|||
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", () => {
|
||||
expect(() => {
|
||||
router.route(123, "/path", () => {});
|
||||
|
@ -213,12 +324,18 @@ describe("SVRouter", () => {
|
|||
}).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(() => {
|
||||
router.pass(123, () => {});
|
||||
}).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", () => {
|
||||
const req = {
|
||||
method: "GET",
|
||||
|
@ -237,4 +354,26 @@ describe("SVRouter", () => {
|
|||
|
||||
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