Segment validators#227
Conversation
This allows to add validator functions at the segment level.
|
Actually I almost opened an issue for this. This is in my opinion one of the biggest missing things right now. So I'm very excited by this. Only question really is API. I'd love to look at more prior art to decide on that because this is a feature I haven't had experience with. I've seen sometimes people put the regex right into the string pattern. But I'm not sure that is any good. |
|
Yeah, I understand, I think you need to play with it before deciding on the API. For example, I made several times the mistake to define my validators with a start colon ":" const segmentValidators: SegmentValidators = {
":number": (v: string) => /^\d+$/.test(v),
":html": (v: string) => /\.html$/.test(v),
};So maybe it's better to with it. I think, DX is really important here. Thanks. |
|
Don't know if this is possible but here is an attempt at brainstorming another possibility... const segmentValidators = {
number: (v: string) => /^\d+$/.test(v),
html: (v: string) => /\.html$/.test(v),
};
<Routes>
<Route
path="/:number/archive/:html"
:number={segmentValidators.number}
:html={segmentValidators.html}
element={<Test />}
/>
<FileRoutes />
</Routes><Routes>
<Route
path="/:number/archive/:html"
:number={/^\d+$/}
:html={/\.html$/}
element={<Test />}
/>
<FileRoutes />
</Routes>Probably not thisconst segmentValidators = {
number: (v: string) => /^\d+$/.test(v),
html: (v: string) => /\.html$/.test(v),
};
<Routes>
<Route
path={{
number: segmentValidators.number,
"archive",
html: segmentValidators.html
}}
element={<Test />}
/>
<FileRoutes />
</Routes><Routes>
<Route
path={{
number: /^\d+$/,
"archive",
html: /\.html$/
}}
element={<Test />}
/>
<FileRoutes />
</Routes>I like that this would be less ambiguous and more coupled, although I wouldn't mind having to use the original suggestion. |
|
Yeah, that is very explicit, but I just found that the downside is it requires more code to write. But I like the expressiveness. |
I guess it depends on how often you expect to reuse the exact matching functions/patterns defined up top for multiple As far as terminology, it looks like Vue docs refer to this as "Custom regex in params" / "custom regex for a param"... Here we are calling it a "segment validator"... I suppose the difference for us is that it could be a full-fledged function that returns I think we can easily first swap "validate" for "match" as in "matching function or regex". As for "segment"... The docs for
The docs for
Which suggests one of these could work in conjunction with "matching function or regex":
It kind of feels like this "matching" behavior has more to do with the idea of a URL/path/route than the actual "params" that get "deserialized" when optionally used in a component. That is to say it has to do more with routing and route matching than it does with the eventual parameters. For this reason I think perhaps there could be a better phrasing than when Vue docs say "custom regex for a param"... In this case it's probably more of a "custom matching function or regex for a dynamic route path URL part/portion/segment". Perhaps refined as: "Custom matching function or regex for a dynamic route path segment." The clarification of dynamic segment and route path terminology might lead one to be more specific in the docs:
To be more like:
|
Yeah, true! Except for regular things like
Agreed for 'match,' it's shorter than 'validate,' and in this context, it has the same meaning. As for naming, I like 'segment' and 'part' words. I'm always confused when using
So 'params' in Solid Start's routing world is always confusing (to me at least). So having a really specific naming for these 'segments' or 'route portions' or 'whatever we decide' feels much needed. I would even argue that renaming |
|
As for terminology, I agree with the use of 'matching' in the context of Routes. The feature you're adding adds additional restrictions to the basic path matching syntax, hence you could name them 'matching criteria'. As for naming the property I would propose 'where' (as used in Laravel) which feels natural in the context of a Route (example below). I do not think the In terms of path syntax, I wouldn't recomend the Vue route. For consistency it is better to have all your criteria in one place, not split between the path and wherever you put the functional constraints. Moreover, a simple path syntax plays very well with Typescript as it can infer parameter names based on the provided path. A small demo of this can be found here (typescript playground). For the functional part I mostly agree with the PR, but would like to add some functionality. Besides a predicate on the string literal, I would like passing a RegExp as a shorthand for testing against the string and arrays as an enum. Wildcard routes can also be handled nicely and typesafe. For example: <Route path="something/:a/:b/:c/*rest" where={{
a: /^\d+$/
b: ["first", "second",
c: (s) => s.length > 3,
rest: (args) => args.length > 2
}}/> |
|
@JorrenH I agree with all your points, except that I don’t like the If you don’t know that this feature exist you’ll never search with |
That's very cool... Not sure if it matters but not sure how well the word "matchers" would translate to non-english language.
Perhaps <Route path="users/:id/projects/:project" matchFilters={{
id: /^\d+$/,
project: (s) => !s.startsWith('draft-')
}} />type MatchFilter =
| string[]
| RegExp
| ((s: string) => boolean)
Perhaps function User() {
const routePath = useRoutePath<{ id: string; project: string }>();
// when url is /users/123/projects/hello-world
console.log(routePath.id, routePath.project);
// 123, hello-world
} |
|
Note the docs for https://start.solidjs.com/api/useMatch describes "dynamic route sections" to mean I think a precedent has already been set by existing libraries using "params" to mean any variable part of the URL... I agree it is not entirely intuitive coming from experience with query params but I can see how they made the leap when naming things like https://reactrouter.com/en/main/hooks/use-params ... Would be less work to just stick with calling the dynamic route sections However, I still think Yeah so I think personally I'd be leaning towards:
|
|
Thank you. This is exactly the sort of discussion I was looking for. MatchFilters seems good. One other example I want to throw out there because we need to consider how this gets into file system routing. Here's SvelteKit's version: https://kit.svelte.dev/docs/advanced-routing#matching They seem to focus on re-usable patterns using a named pattern. Mind you that sort of thing can still be built on top of this. I'm even debating changing the named export in SolidStart more to a route metadata object: export const routeInfo = {
data() {},
matchFilters: {
},
metadata: {}
}In that way we can have access to all the route parameters. Yeah I think I like the direction this discussion is going. |
|
The addition of The approach from SvelteKit is very interesting, and I think named matchFilters would be great to give File routing at least some of the power these filters bring. I'm not too fond of registering named matchers by convention of the directory they are in. Besides, does this work with the treeshaking that happens? Instead I think the following could work well for solid. // define MatchFilter in 'src/some/file/you/choose.ts'
export const isNumber: MatchFilter = (s: string) => /^\d+$/.test(s);// use MatchFilter in 'src/components/App.tsx'
import { isNumber } from '../some/file/you/choose';
// use in programmatic routing
<Route path="products/:id" matchFilters={{ id: isNumber }}/>
// use in filerouting
<FileRoutes namedMatchFilters={{ isNumber }}/>
// routes/
// | produts/
// | | [:id=isNumber].tsxOf course, |
|
I agree for
I like their solution, but (personal taste) I would go for
That’d be great! But at the same time I also tend to agree with @JorrenH argument:
Because (correct me if I’m wrong) Solid Start does not want to impose too much conventions. Adding a property to |
We can't use
In a file system routing system there is no In any case those are SolidStart questions. Sounds like we are good with the solution here. We should get the PR up to date and look at merging. |
Apply matchFilter naming and extend matching functionality
|
Thanks for the contribution @JorrenH 👍 Do you guys think we're good to go? |
|
Ok this is great. I think I'm going to merge this and along with the change from the params have us a 0.7.0 release. |
Hi Ryan,
I have a need for the project I'm currently working on, to have complex path matching rules. To target different routes based on the path.
The mechanism in place allow to match on the shape of the paths. But it's impossible to have 2 different routes for theses 2 paths (path => route which I would express):
/product/soap.html=>/product/:product_type/product/marvelous-soap-12345.html=>/product/:product_slugI have implemented what I call
Segment Validators. I would love to have your opinion on naming and ergonomics.It's a function which is called to validate the segment value against the validator defined in user-land.
For example in a route definition:
:idcould be validated as a integer,:controllercould either "product" or "cart", but nothing else,:slugcould a complexe string that we would test against a regex.I think this is great for the flexibility of the router, because this allows to have routes which share the same shape
/seg1/seg2/seg3but matching different routes. I stop here, I'm sure you've got the point.Every previous tests is passing. I've added new ones for this evolution. And I tested in Solid Start, it works out-the-box when you add route definitions in
root.tsx.I'd love your feedback one this.
Thanks,
Jérémy