diff --git a/runtime/mux.go b/runtime/mux.go index 19255ec441e..64462ec7226 100644 --- a/runtime/mux.go +++ b/runtime/mux.go @@ -532,6 +532,7 @@ type handler struct { } func (s *ServeMux) handleHandler(h handler, w http.ResponseWriter, r *http.Request, pathParams map[string]string) { + r.Pattern = h.pat.GetPathPattern() h.h(w, r.WithContext(withHTTPPattern(r.Context(), h.pat)), pathParams) } diff --git a/runtime/pattern.go b/runtime/pattern.go index e54507145b6..d04d3c42fee 100644 --- a/runtime/pattern.go +++ b/runtime/pattern.go @@ -263,6 +263,38 @@ func (p Pattern) String() string { return "/" + segs } +// GetPathPattern generates path pattern from the Pattern. +// It does mostly the same as Pattern.String except it excludes +// utilities.OpPush and utilities.OpPushM operands from the result. +func (p Pattern) GetPathPattern() string { + var stack []string + for _, op := range p.ops { + switch op.code { + case utilities.OpNop: + continue + case utilities.OpPush: + stack = append(stack, "*") + case utilities.OpLitPush: + stack = append(stack, p.pool[op.operand]) + case utilities.OpPushM: + stack = append(stack, "**") + case utilities.OpConcatN: + n := op.operand + l := len(stack) - n + stack = append(stack[:l], strings.Join(stack[l:], "/")) + case utilities.OpCapture: + n := len(stack) - 1 + // just leave the operand and skip everything else + stack[n] = fmt.Sprintf("{%s}", p.vars[op.operand]) + } + } + segs := strings.Join(stack, "/") + if p.verb != "" { + return fmt.Sprintf("/%s:%s", segs, p.verb) + } + return "/" + segs +} + /* * The following code is adopted and modified from Go's standard library * and carries the attached license. diff --git a/runtime/pattern_test.go b/runtime/pattern_test.go index 47d82aed182..a3e64d6b50b 100644 --- a/runtime/pattern_test.go +++ b/runtime/pattern_test.go @@ -634,3 +634,88 @@ func TestPatternString(t *testing.T) { } } } + +func TestGetPathPattern(t *testing.T) { + for _, spec := range []struct { + ops []int + pool []string + verb string + + want string + }{ + { + want: "/", + }, + { + ops: []int{int(utilities.OpNop), anything}, + want: "/", + }, + { + ops: []int{int(utilities.OpPush), anything}, + want: "/*", + }, + { + ops: []int{int(utilities.OpLitPush), 0}, + pool: []string{"endpoint"}, + want: "/endpoint", + }, + { + ops: []int{int(utilities.OpPushM), anything}, + want: "/**", + }, + { + ops: []int{ + int(utilities.OpPush), anything, + int(utilities.OpConcatN), 1, + }, + want: "/*", + }, + { + ops: []int{ + int(utilities.OpPush), anything, + int(utilities.OpConcatN), 1, + int(utilities.OpCapture), 0, + }, + pool: []string{"name"}, + want: "/{name}", + }, + { + ops: []int{ + int(utilities.OpLitPush), 0, + int(utilities.OpLitPush), 1, + int(utilities.OpPush), anything, + int(utilities.OpConcatN), 2, + int(utilities.OpCapture), 2, + int(utilities.OpLitPush), 3, + int(utilities.OpPushM), anything, + int(utilities.OpLitPush), 4, + int(utilities.OpConcatN), 3, + int(utilities.OpCapture), 6, + int(utilities.OpLitPush), 5, + }, + pool: []string{"v1", "buckets", "bucket_name", "objects", ".ext", "tail", "name"}, + want: "/v1/{bucket_name}/{name}/tail", + }, + { + ops: []int{ + int(utilities.OpLitPush), 0, + int(utilities.OpLitPush), 1, + int(utilities.OpPush), anything, + int(utilities.OpConcatN), 2, + int(utilities.OpCapture), 2, + }, + pool: []string{"v1", "bucket", "name"}, + verb: "LOCK", + want: "/v1/{name}:LOCK", + }, + } { + p, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb) + if err != nil { + t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err) + continue + } + if got, want := p.GetPathPattern(), spec.want; got != want { + t.Errorf("%#v.GetPathPattern() = %q; want %q", p, got, want) + } + } +}