mirror of https://github.com/prometheus/prometheus
263 lines
6.8 KiB
Go
263 lines
6.8 KiB
Go
package runtime
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
|
"google.golang.org/grpc/grpclog"
|
|
)
|
|
|
|
var (
|
|
// ErrNotMatch indicates that the given HTTP request path does not match to the pattern.
|
|
ErrNotMatch = errors.New("not match to the path pattern")
|
|
// ErrInvalidPattern indicates that the given definition of Pattern is not valid.
|
|
ErrInvalidPattern = errors.New("invalid pattern")
|
|
)
|
|
|
|
type op struct {
|
|
code utilities.OpCode
|
|
operand int
|
|
}
|
|
|
|
// Pattern is a template pattern of http request paths defined in github.com/googleapis/googleapis/google/api/http.proto.
|
|
type Pattern struct {
|
|
// ops is a list of operations
|
|
ops []op
|
|
// pool is a constant pool indexed by the operands or vars.
|
|
pool []string
|
|
// vars is a list of variables names to be bound by this pattern
|
|
vars []string
|
|
// stacksize is the max depth of the stack
|
|
stacksize int
|
|
// tailLen is the length of the fixed-size segments after a deep wildcard
|
|
tailLen int
|
|
// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
|
|
verb string
|
|
// assumeColonVerb indicates whether a path suffix after a final
|
|
// colon may only be interpreted as a verb.
|
|
assumeColonVerb bool
|
|
}
|
|
|
|
type patternOptions struct {
|
|
assumeColonVerb bool
|
|
}
|
|
|
|
// PatternOpt is an option for creating Patterns.
|
|
type PatternOpt func(*patternOptions)
|
|
|
|
// NewPattern returns a new Pattern from the given definition values.
|
|
// "ops" is a sequence of op codes. "pool" is a constant pool.
|
|
// "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
|
|
// "version" must be 1 for now.
|
|
// It returns an error if the given definition is invalid.
|
|
func NewPattern(version int, ops []int, pool []string, verb string, opts ...PatternOpt) (Pattern, error) {
|
|
options := patternOptions{
|
|
assumeColonVerb: true,
|
|
}
|
|
for _, o := range opts {
|
|
o(&options)
|
|
}
|
|
|
|
if version != 1 {
|
|
grpclog.Infof("unsupported version: %d", version)
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
|
|
l := len(ops)
|
|
if l%2 != 0 {
|
|
grpclog.Infof("odd number of ops codes: %d", l)
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
|
|
var (
|
|
typedOps []op
|
|
stack, maxstack int
|
|
tailLen int
|
|
pushMSeen bool
|
|
vars []string
|
|
)
|
|
for i := 0; i < l; i += 2 {
|
|
op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]}
|
|
switch op.code {
|
|
case utilities.OpNop:
|
|
continue
|
|
case utilities.OpPush:
|
|
if pushMSeen {
|
|
tailLen++
|
|
}
|
|
stack++
|
|
case utilities.OpPushM:
|
|
if pushMSeen {
|
|
grpclog.Infof("pushM appears twice")
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
pushMSeen = true
|
|
stack++
|
|
case utilities.OpLitPush:
|
|
if op.operand < 0 || len(pool) <= op.operand {
|
|
grpclog.Infof("negative literal index: %d", op.operand)
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
if pushMSeen {
|
|
tailLen++
|
|
}
|
|
stack++
|
|
case utilities.OpConcatN:
|
|
if op.operand <= 0 {
|
|
grpclog.Infof("negative concat size: %d", op.operand)
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
stack -= op.operand
|
|
if stack < 0 {
|
|
grpclog.Print("stack underflow")
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
stack++
|
|
case utilities.OpCapture:
|
|
if op.operand < 0 || len(pool) <= op.operand {
|
|
grpclog.Infof("variable name index out of bound: %d", op.operand)
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
v := pool[op.operand]
|
|
op.operand = len(vars)
|
|
vars = append(vars, v)
|
|
stack--
|
|
if stack < 0 {
|
|
grpclog.Infof("stack underflow")
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
default:
|
|
grpclog.Infof("invalid opcode: %d", op.code)
|
|
return Pattern{}, ErrInvalidPattern
|
|
}
|
|
|
|
if maxstack < stack {
|
|
maxstack = stack
|
|
}
|
|
typedOps = append(typedOps, op)
|
|
}
|
|
return Pattern{
|
|
ops: typedOps,
|
|
pool: pool,
|
|
vars: vars,
|
|
stacksize: maxstack,
|
|
tailLen: tailLen,
|
|
verb: verb,
|
|
assumeColonVerb: options.assumeColonVerb,
|
|
}, nil
|
|
}
|
|
|
|
// MustPattern is a helper function which makes it easier to call NewPattern in variable initialization.
|
|
func MustPattern(p Pattern, err error) Pattern {
|
|
if err != nil {
|
|
grpclog.Fatalf("Pattern initialization failed: %v", err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
// Match examines components if it matches to the Pattern.
|
|
// If it matches, the function returns a mapping from field paths to their captured values.
|
|
// If otherwise, the function returns an error.
|
|
func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
|
|
if p.verb != verb {
|
|
if p.assumeColonVerb || p.verb != "" {
|
|
return nil, ErrNotMatch
|
|
}
|
|
if len(components) == 0 {
|
|
components = []string{":" + verb}
|
|
} else {
|
|
components = append([]string{}, components...)
|
|
components[len(components)-1] += ":" + verb
|
|
}
|
|
verb = ""
|
|
}
|
|
|
|
var pos int
|
|
stack := make([]string, 0, p.stacksize)
|
|
captured := make([]string, len(p.vars))
|
|
l := len(components)
|
|
for _, op := range p.ops {
|
|
switch op.code {
|
|
case utilities.OpNop:
|
|
continue
|
|
case utilities.OpPush, utilities.OpLitPush:
|
|
if pos >= l {
|
|
return nil, ErrNotMatch
|
|
}
|
|
c := components[pos]
|
|
if op.code == utilities.OpLitPush {
|
|
if lit := p.pool[op.operand]; c != lit {
|
|
return nil, ErrNotMatch
|
|
}
|
|
}
|
|
stack = append(stack, c)
|
|
pos++
|
|
case utilities.OpPushM:
|
|
end := len(components)
|
|
if end < pos+p.tailLen {
|
|
return nil, ErrNotMatch
|
|
}
|
|
end -= p.tailLen
|
|
stack = append(stack, strings.Join(components[pos:end], "/"))
|
|
pos = end
|
|
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
|
|
captured[op.operand] = stack[n]
|
|
stack = stack[:n]
|
|
}
|
|
}
|
|
if pos < l {
|
|
return nil, ErrNotMatch
|
|
}
|
|
bindings := make(map[string]string)
|
|
for i, val := range captured {
|
|
bindings[p.vars[i]] = val
|
|
}
|
|
return bindings, nil
|
|
}
|
|
|
|
// Verb returns the verb part of the Pattern.
|
|
func (p Pattern) Verb() string { return p.verb }
|
|
|
|
func (p Pattern) String() 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
|
|
stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n])
|
|
}
|
|
}
|
|
segs := strings.Join(stack, "/")
|
|
if p.verb != "" {
|
|
return fmt.Sprintf("/%s:%s", segs, p.verb)
|
|
}
|
|
return "/" + segs
|
|
}
|
|
|
|
// AssumeColonVerbOpt indicates whether a path suffix after a final
|
|
// colon may only be interpreted as a verb.
|
|
func AssumeColonVerbOpt(val bool) PatternOpt {
|
|
return PatternOpt(func(o *patternOptions) {
|
|
o.assumeColonVerb = val
|
|
})
|
|
}
|