diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-06-27 22:34:36 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-06-27 22:34:36 +0200 |
| commit | 8e9889d3e5b2874854832d1d882d124132546f49 (patch) | |
| tree | 80ab6bf53dac3090630ad58d7ebfbbf2c97019ca /pathrouter/catchallvariable.go | |
| parent | 8804729561d8e5edd7179f494d89c620459d319f (diff) | |
| download | go-gg-8e9889d3e5b2874854832d1d882d124132546f49.tar.gz | |
add package pathrouter
Diffstat (limited to 'pathrouter/catchallvariable.go')
| -rw-r--r-- | pathrouter/catchallvariable.go | 60 |
1 files changed, 60 insertions, 0 deletions
diff --git a/pathrouter/catchallvariable.go b/pathrouter/catchallvariable.go new file mode 100644 index 0000000..1b98672 --- /dev/null +++ b/pathrouter/catchallvariable.go @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net> +// SPDX-License-Identifier: Apache-2.0 + +package pathrouter + +import ( + "strings" + + . "go.xyrillian.de/gg/option" +) + +// CatchAllVariable is a [Matcher] that accepts subpaths with an arbitrary number of path elements. +// The value of any amount of leading path elements will be collected into vars[name], +// such that the remainder is accepted by the next matcher. +// At least one element must be collected into vars[name]. +// +// CatchAllVariable() may appear at any point within the routing tree, +// but it may not contain another CatchAllVariable() anywhere within it. +func CatchAllVariable(name string, matcher Matcher) Matcher { + return catchAllVariable(name, matcher.downcast()) +} + +func catchAllVariable(name string, matcher realMatcher) Matcher { + // NOTE: The specific behavior of CatchAllVariable() is why this package exists in the first place. + // I wanted to replace gorilla/mux with something more performance in Keppel, + // but all the fast routers do not accept catch-all variables in the middle of a path + // like the OCI Distribution API requires (e.g. "/v2/*repo/manifests/:reference" with "repo" being a full path). + + innerMinLength := matcher.minLength + innerMaxLength, ok := matcher.maxLength.Unpack() + if !ok { + panic("matcher within CatchAllVariable() may not accept unlimited path lengths") + } + + accept := func(path []string, vars map[string]string) HandlerFunc { + for length := innerMinLength; length <= innerMaxLength; length++ { + if length > len(path) { + break + } + caughtPath, subpath := path[0:len(path)-length], path[len(path)-length:] + if len(caughtPath) == 0 { + continue + } + + handlerFunc := matcher.accept(subpath, vars) + if handlerFunc == nil { + continue + } + vars[name] = pathUnescape(strings.Join(caughtPath, "/")) + return handlerFunc + } + return nil + } + + return realMatcher{ + minLength: innerMinLength + 1, + maxLength: None[int](), + accept: accept, + } +} |
