fiber icon indicating copy to clipboard operation
fiber copied to clipboard

πŸš€ Request: Route Constraints

Open CerealKiller97 opened this issue 2 years ago β€’ 35 comments

Route constraints execute when a match has occurred to the incoming URL and the URL path is tokenized into route values. I found this very useful when defining routes. ASP NET Core was inspiration for this feature.

Example

  app.Get("/users/{id:int}", func (ctx *fiber.Ctx) {
      // will come here if id is int
     // otherwise return 404
  })

Proposed constraints:

Constraint Example Example matches
int {id:int} 123456789, -123456789
bool {active:bool} true,false
datetime {dob:datetime} 2016-12-31,Β 2016-12-31 7:32pm
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638
float {weight:float} 1.234,Β -1,001.01e8
minlength(value) {username:minlength(4)} Test (must be at least 4 characters)
maxlength(value) {filename:maxlength(8)} MyFile (must be no more than 8 characters
length(length) {filename:length(12)} somefile.txt (exactly 12 characters)
min(value) {age:min(18)} 19 (Integer value must be at least 18)
max(value) {age:max(120)} 91 (Integer value must be no more than 120)
range(min,max) {age:range(18,120)} 91 (Integer value must be at least 18 but no more than 120)
alpha {name:alpha} Rick (String must consist of one or more alphabetical characters,Β a-zΒ and case-insensitive)
regex(expression) {ssn:regex(^\d{{3}}-\d{{2}}-\d{{4}}$)} 123-45-6789 (Must match regular expression)

More details: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0#route-constraints

CerealKiller97 avatar May 08 '22 20:05 CerealKiller97

Thanks for opening your first issue here! πŸŽ‰ Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

welcome[bot] avatar May 08 '22 20:05 welcome[bot]

hello @CerealKiller97, are you working on this feature? if not, can you or @efectn assign it to me, I would love to work on it

MohabMohamed avatar May 09 '22 13:05 MohabMohamed

hello @CerealKiller97, are you working on this feature? if not, can you or @efectn assign it to me, I would love to work on it

Assigned. If @CerealKiller97 isn't working on this, you can start working πŸš€πŸš€

efectn avatar May 09 '22 13:05 efectn

It is looking good. This feature can open from fiber.Config for v2. Example:

fiber.New(fiber.Config{
  EnableRouteConstraints: true,
})

What do you think about it ?

balcieren avatar May 09 '22 13:05 balcieren

It is looking good. This feature can open from fiber.Config for v2. Example:

fiber.New(fiber.Config{
  EnableRouteConstraints: true,
})

What do you think about it ?

I think something like this can be done too instead of adding extra config var:

app.Get("/users/:id:int", func (ctx *fiber.Ctx) {
      // will come here if id is int
     // otherwise return 404
  })

efectn avatar May 09 '22 13:05 efectn

I think something like this can be done too instead of adding extra config var:

I think it is not understandable.

balcieren avatar May 09 '22 13:05 balcieren

My thought was to do it also without config switches and really use the same syntax as in the table, as alternative

Important is the speed, there should be hardly any time added when analyzing the route (so string based detection, except for the part with regexes)

ReneWerner87 avatar May 09 '22 14:05 ReneWerner87

hello @CerealKiller97, are you working on this feature? if not, can you or @efectn assign it to me, I would love to work on it

Hello @MohabMohamed you can work on this. Sorry for waiting for my reply. πŸš€

CerealKiller97 avatar May 09 '22 15:05 CerealKiller97

Hello @MohabMohamed you can work on this. Sorry for waiting for my reply.

Thank you @CerealKiller97

I think something like this can be done too instead of adding extra config var: I think we can do something like this to differentiate between the param name and its type, and without changing unconstrained params:

app.Get("/users/:id<int>", func (ctx *fiber.Ctx) {
      // will come here if id is int
     // otherwise return 404
  })

app.Get("/posts/:id", func (ctx *fiber.Ctx) {

  })

what do you think?

MohabMohamed avatar May 10 '22 10:05 MohabMohamed

would also be possible

ReneWerner87 avatar May 10 '22 10:05 ReneWerner87

but it would be good if we use patterns that are already used in other frameworks/languages

maybe someone can do some research

the goal is to provide people with familiar structures that they have already been exposed to in order to promote acceptance and knowledge sharing

ReneWerner87 avatar May 10 '22 10:05 ReneWerner87

Hello @MohabMohamed you can work on this. Sorry for waiting for my reply.

Thank you @CerealKiller97

I think something like this can be done too instead of adding extra config var: I think we can do something like this to differentiate between the param name and its type, and without changing unconstrained params:

app.Get("/users/:id<int>", func (ctx *fiber.Ctx) {
      // will come here if id is int
     // otherwise return 404
  })

app.Get("/posts/:id", func (ctx *fiber.Ctx) {

  })

what do you think?

I think this is the best way because of fiber router doesn't have adding parameters by {}

efectn avatar May 10 '22 10:05 efectn

I think this is the best way because of fiber router doesn't have adding parameters by {}

well, that would not be too complex to provide this possibility as an alternative syntax

because this "{" is a fixed character you can use it as a delimiter and mark it in the segments of the routes

which has to happen anyway for the constrains

ReneWerner87 avatar May 10 '22 10:05 ReneWerner87

I think this is the best way because of fiber router doesn't have adding parameters by {}

well, that would not be too complex to provide this possibility as an alternative syntax

because this "{" is a fixed character you can use it as a delimiter and mark it in the segments of the routes

which has to happen anyway for the contrains

Yes but Fiber is Express inspired framework. Wouldn't adding {}, make Fiber more complex?

efectn avatar May 10 '22 10:05 efectn

I think if we want to add it with { } we could do this in V3 and change the params without constraints to follow this pattern too, but we can't do this with the already in use version,and I think the constrained parameters should be derived from the regular params

and the only constrained params shape I know is flask it like this:

/users/<int:id>

MohabMohamed avatar May 10 '22 10:05 MohabMohamed

maybe but since express does not offer this feature we have to choose a structure

would ask for research a small list of frameworks/languages that offer this feature and their structure, i think this can only be beneficial and we might find some more additional constrains

ReneWerner87 avatar May 10 '22 10:05 ReneWerner87

I think if we want to add it with { } we could do this in V3 and change the params without constraints to follow this pattern too, but we can't do this with the already in use version,and I think the constrained parameters should be derived from the regular params

think we can do this in version 2 as well since we still support the old notation we don't change anything just add functionality

ReneWerner87 avatar May 10 '22 10:05 ReneWerner87

https://laravel.com/docs/9.x/routing#parameters-regular-expression-constraints image

https://www.c-sharpcorner.com/blogs/asp-net-core-route-constraints

image

... maybe some more research

ReneWerner87 avatar May 10 '22 10:05 ReneWerner87

we could also leave this declaration completely out of the url and provide a method in the context which receives an object with the constrains and validates it

// instead of "/users/:id<int>"
app.Get("/users/:id", func (ctx *fiber.Ctx) {
      err := ctx.CheckRouteParams(fiber.Map{
          id: "int",
      })
  })
// instead of "/users/:id<int>"
app.Get("/users/:id", func (ctx *fiber.Ctx) {
    err := ctx.RouteConstrains().whereNumber("id")(....more...checks....).check();
})

ReneWerner87 avatar May 10 '22 10:05 ReneWerner87

we could also leave this declaration completely out of the url and provide a method in the context which receives an object with the constrains and validates it

// instead of "/users/:id<int>"
app.Get("/users/:id", func (ctx *fiber.Ctx) {
      err := ctx.CheckRouteParams(fiber.Map{
          id: "int",
      })
  })
// instead of "/users/:id<int>"
app.Get("/users/:id", func (ctx *fiber.Ctx) {
    err := ctx.RouteConstrains().whereNumber("id")(....more...checks....).check();
})

We can also do it by using latestRoute var of App struct -similar to usage of Name()-

app.Get("/users/:id", func (ctx *fiber.Ctx) {
    ...
}).WhereNumber("id")

However, id<int> way sounds better.

efectn avatar May 10 '22 10:05 efectn

I don't prefer chaining as it will make sometimes a long chain of function calls in the client code. this sounds good :

// instead of "/users/:id<int>"
app.Get("/users/:id", func (ctx *fiber.Ctx) {
      err := ctx.CheckRouteParams(fiber.Map{
          id: "int",
      })
  })

and also this:

However, id<int> way sounds better.

so what's your final decision? so I can start working on this.

MohabMohamed avatar May 10 '22 14:05 MohabMohamed

The route syntax is okay for me.

ReneWerner87 avatar May 10 '22 15:05 ReneWerner87

Just know that later there will be questions or feature requests if the error messages we generate due to a mismatch are customizable.

So we should create formatted strings for this, which are customizable from outside (please not via config -> already quite a lot of settings)

ReneWerner87 avatar May 10 '22 15:05 ReneWerner87

Or ask if we can customize the structure (json, xml, html) and the status code

If a mismatch is detected, we should pass the error message which is composed of the type and the parameter name as a string into a generator function, which then generates the response.

so that we can make it overloadable and give the user all possibilities

ReneWerner87 avatar May 10 '22 15:05 ReneWerner87

I think we should just return 404 as not matching the type of the param should be the same behavior as not matching any route. and if the user wants to create a custom response when matching, he could use :id notation after the specific type one. so fiber while skipping the constrained routes and then match the generic one and the user can do what ever he wants in this route.

or do you have another idea?

MohabMohamed avatar May 11 '22 12:05 MohabMohamed

yes would go, but would not be quite optimal

users will think that there is already a detection of which parameters do not match and expect this information to be returned to the consumer of the api with the exact instructions about the error

don't think users will be happy if a route constrains feature exists and because they want to customize the response in case it doesn't match, they have to rebuild the entire detection feature and generate their own response

we should provide in the case the possibility to customize the response

ReneWerner87 avatar May 11 '22 12:05 ReneWerner87

image the c# feature also has a custom error message with the parameters that do not match

ReneWerner87 avatar May 11 '22 12:05 ReneWerner87

if a mismatch happened we can include this data in the ctx and we can implement a standard middleware to return these errors and run it after the route. so the user can use it if he wants or implement his own. I think that would be a clean solution.

MohabMohamed avatar May 11 '22 12:05 MohabMohamed

okay gladly, as long as this does not produce too many allocations, if we keep this data available that is acceptable

performance and simplicity is fiber's recipe for success -> don't want to change anything and continue this idea

ReneWerner87 avatar May 11 '22 12:05 ReneWerner87

Does adding too much option makes fiber more complex in terms of developer's perspective? or compilation time? Silly question, why can't we just use regex?

In gorilla/mux we do it this way /users/{id:[0-9]+} it means we want to disregard /users/whatever in the routing and only accept int/numeric value.

This is standard in laravel/expressjs too, this is even notable part of expressjs where there are insane regex registration that we can apply, such as:

// from: https://expressjs.com/en/guide/routing.html

/user/:userId(\d+)

If performance is really the problem then maybe adding that fiber config should disregard regex checking

daison12006013 avatar May 21 '22 17:05 daison12006013