Skip to content
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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot have paths /user/me and /user/:id/posts at the same time #112

Closed
ValeriaVG opened this issue Dec 5, 2021 · 8 comments · Fixed by #115
Closed

Cannot have paths /user/me and /user/:id/posts at the same time #112

ValeriaVG opened this issue Dec 5, 2021 · 8 comments · Fixed by #115

Comments

@ValeriaVG
Copy link
Contributor

Currently, it's not possible to create the following endpoints at the same time:

method=GET path=/user/:id
method=GET path=/user/:id/posts

Paths with parameters as the first part are also conflicting with any other non-parametrical paths:

method=GET path=/users
method=GET path=/:username/posts

It'd be very nice to support these kinds of paths without conflicts, would it be possible?

@eandre
Copy link
Member

eandre commented Dec 6, 2021

Thanks @ValeriaVG for the report. I can't reproduce the first example you gave:

method=GET path=/user/:id
method=GET path=/user/:id/posts

Can you provide a more complete reproducer? That should definitely work.

As for your second example, that's trickier to support. If you allow that you end up in the situation where multiple routes can match the same URL, which turns routing from an O(1) to an O(n) problem. Currently we require that routes are unambiguous in their structure, which prevents us from supporting /users and /:username since which route to send to depends on the precise value of the first path parameter. This can be a problem when you for example allow people to sign up with username == "users" and puts a burden on API consumers to deal with such special cases. The current restriction in API path naming prevents this class of problem.

@ValeriaVG
Copy link
Contributor Author

Apologies, indeed the first use case was conflicting with another path /user/me in my case, which makes sense along with the restriction.

@ValeriaVG ValeriaVG reopened this Dec 6, 2021
@ValeriaVG
Copy link
Contributor Author

ValeriaVG commented Dec 6, 2021

Sorry, no it's actually not working:

❌ Building Encore application graph... Failed: posts/posts.go:16:1: invalid API path: cannot combine parameter ':id' with path '/user/me' (other declaration at .../path-example/posts/posts.go:9:1) (and 1 more errors)

Here's an example:

package posts

import "context"

type Message struct {
	Message string
}

//encore:api public method=GET path=/user/me
func User(ctx context.Context) (*Message, error) {
	return &Message{
		Message: "me",
	}, nil
}

//encore:api public method=GET path=/user/:id/posts
func UserPosts(ctx context.Context, id string) (*Message, error) {
	return &Message{
		Message: id,
	}, nil
}

@ValeriaVG ValeriaVG changed the title Paths starting with a parameter conflict with all the others Cannot have paths /user/me and /user/:id/posts at the same time Dec 6, 2021
@eandre
Copy link
Member

eandre commented Dec 6, 2021

Yes, that is the same as the second case from the original post I believe. The target endpoint becomes dependent on the value of the :id parameter, where if it happens to be equal to me it would call one endpoint and otherwise another endpoint. I would either name the "get current user" endpoint just /me or /users/me or similar, if that works?

@ValeriaVG
Copy link
Contributor Author

ValeriaVG commented Dec 6, 2021

No, of course, I'm using a different path, this is not an issue.
But why then /users/:username and /users/:username/posts are allowed, but not /users/me and /users/:username/posts?

Following the same logic if the username is something/posts it will end up in a conflict that you are describing.

As in this will compile:

package posts

import "context"

type Message struct {
	Message string
}

//encore:api public method=GET path=/user/:username
func User(ctx context.Context, username string) (*Message, error) {
	return &Message{
		Message: username,
	}, nil
}

//encore:api public method=GET path=/user/:username/posts
func UserPosts(ctx context.Context, username string) (*Message, error) {
	return &Message{
		Message: username,
	}, nil
}

But not this:

package posts

import "context"

type Message struct {
	Message string
}

//encore:api public method=GET path=/user/me
func User(ctx context.Context) (*Message, error) {
	return &Message{
		Message: "me",
	}, nil
}

//encore:api public method=GET path=/user/:username/posts
func UserPosts(ctx context.Context, username string) (*Message, error) {
	return &Message{
		Message: username,
	}, nil
}

@eandre
Copy link
Member

eandre commented Dec 6, 2021

When you use the :username it's limited to a single path segment, so /user/:username could never conflict with /user/:username/posts. That said I see why it's confusing that /user/me is disallowed when you don't have a /user/:username route defined. I agree that is unambiguous and should probably be allowed. That is currently a limitation in the library we use (https://github.com/julienschmidt/httprouter).

There's a similar issue where httprouter prevents the use of different names for the path segment for different routes (that is you can't have /user/:username/posts and /user/:name/comments — the path segment name must be the same in both cases). Perhaps we should fork httprouter and extend it to support these cases.

@ValeriaVG
Copy link
Contributor Author

Gotcha!
I'm guessing that's the issue julienschmidt/httprouter#183
It seems to be a heated discussion there regarding if it should be supported or not.

I think it would be easier to document this as a known limitation or recommendation and see if it gets eventually resolved in the underlying library.

Let me know if I can help with that, otherwise, feel free to close the issue :-)

@eandre
Copy link
Member

eandre commented Dec 7, 2021

Yeah that's a great point. I definitely want to support this use case better, but as a first action documenting the current behavior is wise. We'll get around to it but if you feel like writing something up that would also be great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants