New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Option to align route params with schema at startup #5215
Comments
馃憢 Rather this sounds like a good idea for a plugin, would you like to create one? |
We have something similar here:
The hardest thing is to list the path parameters because they are a router's internal info |
馃憢 Hi, I guess to make a standalone plugin we need somehow to leak the router's internal or expose them. |
I'd say that leaking the router as is can be pretty dangerous as it's easy to alter it and make Potentially, have we already considered exposing an abstraction of I can see potential use cases there (like this plugin). Although I've not tested if it returns the |
Makes sense, Ill try to give it a shot to see whether we can abstract it so that other plugins can use it. |
Tried a quick implementation for opening a discussion wether is better to extract into a standalone plugin (what scope would it add? just to validate routes with params?) |
The PR LGTM overall, but I'm over the fence with having the validation within core. This seems to have pretty specific use cases, and the harm of unexpected behaviour can cause friction. That's one of the main reasons to suggest develop a community plugin instead |
Should this be closed due to #5230 or do we want to keep it open for the plugin discussion? |
I think it should be in core. It feels somehow wrong to load a plugin, to "just" check if routes are registered correctly. |
We support multiple validators, and we can't safely implement this. |
I believe we should keep it open for plugin discussion |
To me, a nice plugin I would use to validate the parameters would be: Plugin proposal adding a new hook
Advantages
Cons
Plugin proposal using existent hooks
Advantages
Cons
|
We have already onRoute hook, which is triggered when a route is added. |
Ouch, you're right. Thanks for the quick reply and the insight. What you think giving it a shot? Do you have any other community plugins that are responsible for validating routes just check them out? |
@Uzlopak I tried to create the abovementioned plugin but I've encountered an issue, the fastify.addHook('onRoute', (routeOpts) => {
const {method, url} = routeOpts.
const route = fastify.findRoute({method, url});
console.log(route); // null
})
fastify.get('/index', (req, reply) => reply.send("OK")); Any specific reason why the hooks are called here and the actual registration happens here ? To overcome this without touching the core, I could parse the |
I think it would be better to extract the parameter determination functionality in https://github.com/delvedor/find-my-way/blob/a4d92627b818b42cb682fb2c808e1ca694b215f5/index.js#L171 and then use it. But I wonder what you can do with regex values for route?! |
The reported issue here was that there are no checks if the registered params inside the route is the same as the one inside the declared schema. fastify.get('/artists/:artistId', {
schema: { params: { id: { type: 'integer' } } }
}, handler); In this case the application would start correctly but hiding silent errors due to mismatch between declared params inside URL and the one declared in the schema. The plugin to me, should check that the parsed params are the same as the ones declared inside the schema.
You mean exposing the functionality from fmw that will be exposed from fastify core (to avoid dependency leaks)? |
Hmm, I think we are going down the rabbit hole; setting fmw aside to make the plugin fit the use case will be slightly more problematic than we thought. Have you considered doing the registration by yourself using You can also use |
Yes. You don't need to call fmw. You have all the schema inside routeOptions. |
Yes, you have the schema but you don't have the parsed params from the route path. {
schema: { params: { id: 'integer' } },
params: { id: ':id' },
...otherFmwRouteFields
}
I totally agree with this and I think is getting much more complicated than the problem it tries to solve. |
Wouldn't it as simple as the following check? fastify.addHook('onRoute', function(option) {
// check only when schema provided
if(option.schema && option.schema.params) {
for(const param of Object.keys(option.schema.params)) {
// here is simple, named param actually always start with colon and follow the param name
// so, what we need to do is check by includes
if(!option.path.includes(`:${param}`)) throw Error(`${option.path} does not include param ${param}`)
}
}
}) Even better for bidirectional check. const COLON = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
function extractParamUrl (str) {
const params = []
let i, char
let state = 'skip'
let param = ''
let level = 0
// count for regex if no param exist
let regexp = 0
for (i = 0; i < str.length; i++) {
char = str[i]
switch (state) {
case 'colon': {
// we only accept a-zA-Z0-9_ in param
if (COLON.indexOf(char) !== -1) {
param += char
} else if (char === '(') {
state = 'regexp'
level++
} else {
// end
state = 'skip'
params.push(param)
param = ''
}
break
}
case 'regexp': {
if (char === '(') {
level++
} else if (char === ')') {
level--
}
// we end if the level reach zero
if (level === 0) {
state = 'skip'
if (param === '') {
// no idea how to handle non-named param
param = String(regexp)
}
params.push(param)
param = ''
}
break
}
default: {
// we check if we need to change state
if (char === ':' && str[i + 1] === ':') {
// double colon -> single colon
path += char
// skip one more
i++
} else if (char === ':') {
// single colon -> state colon
state = 'colon'
} else if (char === '(') {
state = 'regexp'
level++
} else if (char === '*') {
// * -> {*}
// should be exist once only
params.push('*')
}
}
}
}
// clean up
if (state === 'colon' && param !== '') {
params.push(param)
}
return params
}
fastify.addHook('onRoute', function(option) {
// check only when schema provided
if(option.schema && option.schema.params) {
const urlParams = extractParamUrl(option.path)
const schemaParams = Object.keys(option.schema.params)
for(const param of schemaParams ) {
if(!urlParams.includes(param)) throw Error(`${option.path} does not include param ${param}`)
}
for(const param of urlParams) {
if(!schemaParams.includes(param)) throw Error(`${param} does not include schema but exist inside path ${option.path}`)
}
}
}) |
Yes, this was exactly what we proposed along the thread but I fear that there will be a dependency leak between our param extraction and the one used by fmw internally. To me, to create a stable plugin, the params parsed from the fmw should be exactly the same that we use to check against the schema.
But, if you think could be worth to implement another extraction algorithm I can try to write a plugin to solve the issue 馃檶 |
@mcollina We need to re-considerate should be we expose all the information on route and hidden field on Anyway, @sf3ris you can achieve it by const kRoutes = Symbol('routes')
fastify.decorate(kRoutes, [])
fastify.addHook('onRoute', function(options) {
fastify[kRoutes].push(options)
})
fastify.addHook('onReady', function() {
// we can now check each routes
for(const option of fastify[kRoutes]) {
const schema = option.schema
const route = fastify.findRoute({
url: option.url,
method: option.method,
constraints: option.constraints
})
if(schema && schema.params) {
// we check only when params schema exists
const schemaParams = Object.keys(schema.params.properties)
const urlParams = Object.keys(route.params)
for(const param of schemaParams ) {
if(!urlParams.includes(param)) throw Error(`${option.path} does not include param ${param}`)
}
for(const param of urlParams) {
if(!schemaParams.includes(param)) throw Error(`${param} does not include schema but exist inside path ${option.path}`)
}
}
}
}) |
Prerequisites
馃殌 Feature Proposal
Introduce startup validation in Fastify to check consistency between route parameters in path strings and schema definitions.
Motivation
Ensuring route parameter consistency to prevent runtime errors
Example
A pre-runtime check could catch these mismatches early, enhancing developer experience and application stability.
The text was updated successfully, but these errors were encountered: