generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for ingress path params
- Loading branch information
1 parent
f3fb34d
commit 02135eb
Showing
7 changed files
with
175 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package controller | ||
|
||
import ( | ||
"context" | ||
"math/rand" | ||
"strings" | ||
|
||
"github.com/TBD54566975/ftl/backend/common/slices" | ||
"github.com/TBD54566975/ftl/backend/controller/dal" | ||
"github.com/alecthomas/errors" | ||
) | ||
|
||
func (s *Service) getIngressRoute(ctx context.Context, method string, path string) (*dal.IngressRoute, error) { | ||
pathStart := strings.Split(path, "/")[1] | ||
routes, err := s.dal.GetIngressRoutes(ctx, method, "/"+pathStart) | ||
if err != nil { | ||
return nil, err | ||
} | ||
matchedRoutes := slices.Filter(routes, func(route dal.IngressRoute) bool { | ||
return matchURL(route.Path, path) | ||
}) | ||
if len(matchedRoutes) == 0 { | ||
return nil, dal.ErrNotFound | ||
} | ||
|
||
// TODO: add load balancing at some point | ||
route := matchedRoutes[rand.Intn(len(matchedRoutes))] //nolint:gosec | ||
return &route, nil | ||
} | ||
|
||
func getPathParams(pattern, urlPath string) (map[string]string, error) { | ||
formatSegments := strings.Split(strings.Trim(pattern, "/"), "/") | ||
urlSegments := strings.Split(strings.Trim(urlPath, "/"), "/") | ||
|
||
if len(formatSegments) != len(urlSegments) { | ||
return nil, errors.New("number of segments in pattern and URL path don't match") | ||
} | ||
|
||
params := make(map[string]string) | ||
for i, segment := range formatSegments { | ||
if strings.HasPrefix(segment, "{") && strings.HasSuffix(segment, "}") { | ||
key := strings.Trim(segment, "{}") | ||
params[key] = urlSegments[i] | ||
} | ||
} | ||
return params, nil | ||
} | ||
|
||
func matchURL(pattern, urlPath string) bool { | ||
patternSegments := strings.Split(pattern, "/") | ||
urlSegments := strings.Split(urlPath, "/") | ||
|
||
if len(patternSegments) != len(urlSegments) { | ||
return false | ||
} | ||
|
||
for i := range patternSegments { | ||
if strings.HasPrefix(patternSegments[i], "{") && strings.HasSuffix(patternSegments[i], "}") { | ||
continue // This is a dynamic segment; skip exact match check | ||
} | ||
if patternSegments[i] != urlSegments[i] { | ||
return false | ||
} | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package controller | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/alecthomas/errors" | ||
) | ||
|
||
func TestMatchURL(t *testing.T) { | ||
tests := []struct { | ||
pattern string | ||
urlPath string | ||
expected bool | ||
}{ | ||
{"", "", true}, | ||
{"/", "/", true}, | ||
{"/users", "/users", true}, | ||
{"/users/{id}", "/users/123", true}, | ||
{"/users/{id}/posts", "/users/123/posts", true}, | ||
{"/users/{id}/posts/{postID}", "/users/123/posts/456", true}, | ||
{"/users/{id}/posts/{postID}", "/users/123/posts", false}, | ||
{"/users/{id}/posts/{postID}", "/users/123/posts/456/comments", false}, | ||
{"/users/{id}/posts/{postID}/comments", "/users/123/posts/456/comments", true}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}", "/users/123/posts/456/comments/789", true}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}", "/users/123/posts/456/comments", false}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}", "/users/123/posts/456/comments/789/replies", false}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}/replies", "/users/123/posts/456/comments/789/replies", true}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}/replies/{replyID}", "/users/123/posts/456/comments/789/replies/987", true}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}/replies/{replyID}", "/users/123/posts/456/comments/789/replies", false}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}/replies/{replyID}", "/users/123/posts/456/comments/789/replies/987/extra", false}, | ||
} | ||
|
||
for _, test := range tests { | ||
actual := matchURL(test.pattern, test.urlPath) | ||
if actual != test.expected { | ||
t.Errorf("matchURL(%q, %q) = %v, expected %v", test.pattern, test.urlPath, actual, test.expected) | ||
} | ||
} | ||
} | ||
|
||
func TestGetPathParams(t *testing.T) { | ||
segmentsError := errors.New("number of segments in pattern and URL path don't match") | ||
|
||
tests := []struct { | ||
pattern string | ||
urlPath string | ||
expected map[string]string | ||
err error | ||
}{ | ||
{"", "", map[string]string{}, nil}, | ||
{"/", "/", map[string]string{}, nil}, | ||
{"/users", "/users", map[string]string{}, nil}, | ||
{"/users/{id}", "/users/123", map[string]string{"id": "123"}, nil}, | ||
{"/users/{id}/posts", "/users/123/posts", map[string]string{"id": "123"}, nil}, | ||
{"/users/{id}/posts/{postID}", "/users/123/posts/456", map[string]string{"id": "123", "postID": "456"}, nil}, | ||
{"/users/{id}/posts/{postID}", "/users/123/posts", nil, segmentsError}, | ||
{"/users/{id}/posts/{postID}", "/users/123/posts/456/comments/", nil, segmentsError}, | ||
{"/users/{id}/posts/{postID}/comments", "/users/123/posts/456/comments", map[string]string{"id": "123", "postID": "456"}, nil}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}", "/users/123/posts/456/comments/789", map[string]string{"id": "123", "postID": "456", "commentID": "789"}, nil}, | ||
{"/users/{id}/posts/{postID}/comments/{commentID}", "/users/123/posts/456/comments", nil, segmentsError}, | ||
{"/users/{id}/posts/{year}/{month}/{day}", "/users/123/posts/2023/11/12", map[string]string{"id": "123", "year": "2023", "month": "11", "day": "12"}, nil}, | ||
} | ||
|
||
for _, test := range tests { | ||
actual, err := getPathParams(test.pattern, test.urlPath) | ||
if !areMapsEqual(actual, test.expected) { | ||
t.Errorf("getPathParams(%q, %q) = %v, expected %v", test.pattern, test.urlPath, actual, test.expected) | ||
} | ||
if (err != nil && test.err == nil) || (err == nil && test.err != nil) || (err != nil && test.err != nil && err.Error() != test.err.Error()) { | ||
t.Errorf("getPathParams(%q, %q) returned error %v, expected %v", test.pattern, test.urlPath, err, test.err) | ||
} | ||
} | ||
} | ||
|
||
// Helper function to compare two maps | ||
func areMapsEqual(a, b map[string]string) bool { | ||
if len(a) != len(b) { | ||
return false | ||
} | ||
for key, value := range a { | ||
if bValue, ok := b[key]; !ok || bValue != value { | ||
return false | ||
} | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters