Route-Exclusive Custom Constraint Strategy
joelnet opened this issue · 4 comments
First of all, cheers 🍻 to all the maintainers of this amazing project. Thank you for all your hard work!
The documentation states:
Constraint strategies should be careful to make the deriveConstraint function performant as it is run for every request matched by the router.
I am seeking a way to limit the impact of the deriveConstraint
function to only the routes in need of the constraint. This has been previously discussed #165.
The goals:
- Constraints should not be run on non-constrained routes.
- If a constraint fails, router should fallback to next matching route.
I did create a hackish solution that involves keeping a second router as an allow list. It works, but I am not completely in love with this.
The solution relies on some Fastify methods, so example isn't limited to find-my-way
.
// @ts-check
import Fastify from "fastify";
import fp from "fastify-plugin";
import FindMyWay from "find-my-way";
const port = 8888;
const host = "0.0.0.0";
/**
* @param {import('fastify').FastifyInstance} fastify
*/
const featurePlugin = fp(async (fastify) => {
async function expensiveAsyncCall() {
console.log("!!! expensiveAsyncCall !!!");
return true;
}
const routesAllow = FindMyWay();
/** @type {any} */
const featureConstraint = {
// strategy name for referencing in the route handler `constraints` options
name: "feature",
// storage factory for storing routes in the find-my-way route tree
storage() {
let state = {};
return {
get: (key) => {
return state[key] || null;
},
set: (key, store) => {
state[key] = store;
},
};
},
// function to get the value of the constraint from each incoming request
deriveConstraint(req, ctx, done) {
const route = routesAllow.find(req.method, req.url);
if (!route) return done(null, null);
expensiveAsyncCall().then(
(data) => done(null, data),
(err) => done(err, null)
);
},
};
fastify.addHook("onRoute", (route) => {
if (route.constraints && featureConstraint.name in route.constraints) {
// Create Mock Route for "find" in deriveConstraint.
routesAllow.on(route.method, route.path, () => {});
}
});
fastify.addConstraintStrategy(featureConstraint);
});
async function main() {
const fastify = Fastify();
await fastify.register(featurePlugin);
// Constraint should NOT be run here
fastify.get("/always", () => {
return { route: "/always" };
});
fastify.get("/sometimes", { constraints: { feature: true } }, () => {
return { route: "/sometimes" };
});
// Constraint should NOT be run here
fastify.get("*", () => {
return { route: "*" };
});
await fastify.listen({ port, host });
console.log(`Listening on http://${host}:${port}/`);
console.log(` http://${host}:${port}/always`);
console.log(` http://${host}:${port}/sometimes`);
console.log(` http://${host}:${port}/catchall`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
The above code has a function expensiveAsyncCall
that is only run for the /sometimes
route.
if feature
is set to false
or expensiveAsyncCall
returns false
, the route will fallback to the "*"
route.
I am not sure exactly what I am seeking here. Feedback? Direction? Help?
Hi, thank you for your proposal. It makes much more sense since we added async constraints, and I think we should add it. Although it wouldn't be an easy PR for a couple of reasons:
- Sync constraints should be as fast as they are.
- We shouldn't call the same strategy a second time if the first route fails. We should cache these values.
- find, and lookup methods should have the same interfaces and expected behavior unless we decide to release a new major for this feature.
It might take some time to add this.
I briefly looked at the source and it does indeed look like a complicated change. The complexity is mostly due to the constraints being fully derived prior to call to find
.
/** code has been simplified **/
this.constrainer.deriveConstraints(req, ctx, (err, constraints) => {
const handle = this.find(req.method, req.url, constraints)
})
It looks like find
(and thus HandlerStorage
) would have to be modified to support an asynchronous, on-the-fly lookup of constraints.
From reading all the docs, it seemed like a constraint that failed validate
would not apply to a route. Unfortunately, this is not the case and the whole server startup fails.
From reading all the docs, it seemed like a constraint that failed
validate
would not apply to a route. Unfortunately, this is not the case and the whole server startup fails.
- Can you point to the place in the docs where it's said.
- It sounds like a completely different problem, so open a new issue.