github.com/erda-project/erda-infra@v1.0.9/pkg/transport/http/runtime/pattern.go (about)

     1  // Copyright (c) 2021 Terminus, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Reference: https://github.com/grpc-ecosystem/grpc-gateway/blob/v2.3.0/runtime/pattern.go
    16  
    17  package runtime
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/erda-project/erda-infra/pkg/transport/http/utilities"
    25  	"google.golang.org/grpc/grpclog"
    26  )
    27  
    28  var (
    29  	// ErrNotMatch indicates that the given HTTP request path does not match to the pattern.
    30  	ErrNotMatch = errors.New("not match to the path pattern")
    31  	// ErrInvalidPattern indicates that the given definition of Pattern is not valid.
    32  	ErrInvalidPattern = errors.New("invalid pattern")
    33  )
    34  
    35  type op struct {
    36  	code    utilities.OpCode
    37  	operand int
    38  }
    39  
    40  // Pattern is a template pattern of http request paths defined in
    41  // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
    42  type Pattern struct {
    43  	// ops is a list of operations
    44  	ops []op
    45  	// pool is a constant pool indexed by the operands or vars.
    46  	pool []string
    47  	// vars is a list of variables names to be bound by this pattern
    48  	vars []string
    49  	// stacksize is the max depth of the stack
    50  	stacksize int
    51  	// tailLen is the length of the fixed-size segments after a deep wildcard
    52  	tailLen int
    53  	// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
    54  	verb string
    55  }
    56  
    57  // NewPattern returns a new Pattern from the given definition values.
    58  // "ops" is a sequence of op codes. "pool" is a constant pool.
    59  // "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
    60  // "version" must be 1 for now.
    61  // It returns an error if the given definition is invalid.
    62  func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
    63  	if version != 1 {
    64  		grpclog.Infof("unsupported version: %d", version)
    65  		return Pattern{}, ErrInvalidPattern
    66  	}
    67  
    68  	l := len(ops)
    69  	if l%2 != 0 {
    70  		grpclog.Infof("odd number of ops codes: %d", l)
    71  		return Pattern{}, ErrInvalidPattern
    72  	}
    73  
    74  	var (
    75  		typedOps        []op
    76  		stack, maxstack int
    77  		tailLen         int
    78  		pushMSeen       bool
    79  		vars            []string
    80  	)
    81  	for i := 0; i < l; i += 2 {
    82  		op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]}
    83  		switch op.code {
    84  		case utilities.OpNop:
    85  			continue
    86  		case utilities.OpPush:
    87  			if pushMSeen {
    88  				tailLen++
    89  			}
    90  			stack++
    91  		case utilities.OpPushM:
    92  			if pushMSeen {
    93  				grpclog.Infof("pushM appears twice")
    94  				return Pattern{}, ErrInvalidPattern
    95  			}
    96  			pushMSeen = true
    97  			stack++
    98  		case utilities.OpLitPush:
    99  			if op.operand < 0 || len(pool) <= op.operand {
   100  				grpclog.Infof("negative literal index: %d", op.operand)
   101  				return Pattern{}, ErrInvalidPattern
   102  			}
   103  			if pushMSeen {
   104  				tailLen++
   105  			}
   106  			stack++
   107  		case utilities.OpConcatN:
   108  			if op.operand <= 0 {
   109  				grpclog.Infof("negative concat size: %d", op.operand)
   110  				return Pattern{}, ErrInvalidPattern
   111  			}
   112  			stack -= op.operand
   113  			if stack < 0 {
   114  				grpclog.Info("stack underflow")
   115  				return Pattern{}, ErrInvalidPattern
   116  			}
   117  			stack++
   118  		case utilities.OpCapture:
   119  			if op.operand < 0 || len(pool) <= op.operand {
   120  				grpclog.Infof("variable name index out of bound: %d", op.operand)
   121  				return Pattern{}, ErrInvalidPattern
   122  			}
   123  			v := pool[op.operand]
   124  			op.operand = len(vars)
   125  			vars = append(vars, v)
   126  			stack--
   127  			if stack < 0 {
   128  				grpclog.Infof("stack underflow")
   129  				return Pattern{}, ErrInvalidPattern
   130  			}
   131  		default:
   132  			grpclog.Infof("invalid opcode: %d", op.code)
   133  			return Pattern{}, ErrInvalidPattern
   134  		}
   135  
   136  		if maxstack < stack {
   137  			maxstack = stack
   138  		}
   139  		typedOps = append(typedOps, op)
   140  	}
   141  	return Pattern{
   142  		ops:       typedOps,
   143  		pool:      pool,
   144  		vars:      vars,
   145  		stacksize: maxstack,
   146  		tailLen:   tailLen,
   147  		verb:      verb,
   148  	}, nil
   149  }
   150  
   151  // MustPattern is a helper function which makes it easier to call NewPattern in variable initialization.
   152  func MustPattern(p Pattern, err error) Pattern {
   153  	if err != nil {
   154  		grpclog.Fatalf("Pattern initialization failed: %v", err)
   155  	}
   156  	return p
   157  }
   158  
   159  // Match examines components if it matches to the Pattern.
   160  // If it matches, the function returns a mapping from field paths to their captured values.
   161  // If otherwise, the function returns an error.
   162  func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
   163  	if p.verb != verb {
   164  		if p.verb != "" {
   165  			return nil, ErrNotMatch
   166  		}
   167  		if len(components) == 0 {
   168  			components = []string{":" + verb}
   169  		} else {
   170  			components = append([]string{}, components...)
   171  			components[len(components)-1] += ":" + verb
   172  		}
   173  	}
   174  
   175  	var pos int
   176  	stack := make([]string, 0, p.stacksize)
   177  	captured := make([]string, len(p.vars))
   178  	l := len(components)
   179  	for _, op := range p.ops {
   180  		switch op.code {
   181  		case utilities.OpNop:
   182  			continue
   183  		case utilities.OpPush, utilities.OpLitPush:
   184  			if pos >= l {
   185  				return nil, ErrNotMatch
   186  			}
   187  			c := components[pos]
   188  			if op.code == utilities.OpLitPush {
   189  				if lit := p.pool[op.operand]; c != lit {
   190  					return nil, ErrNotMatch
   191  				}
   192  			}
   193  			stack = append(stack, c)
   194  			pos++
   195  		case utilities.OpPushM:
   196  			end := len(components)
   197  			if end < pos+p.tailLen {
   198  				return nil, ErrNotMatch
   199  			}
   200  			end -= p.tailLen
   201  			stack = append(stack, strings.Join(components[pos:end], "/"))
   202  			pos = end
   203  		case utilities.OpConcatN:
   204  			n := op.operand
   205  			l := len(stack) - n
   206  			stack = append(stack[:l], strings.Join(stack[l:], "/"))
   207  		case utilities.OpCapture:
   208  			n := len(stack) - 1
   209  			captured[op.operand] = stack[n]
   210  			stack = stack[:n]
   211  		}
   212  	}
   213  	if pos < l {
   214  		return nil, ErrNotMatch
   215  	}
   216  	bindings := make(map[string]string)
   217  	for i, val := range captured {
   218  		bindings[p.vars[i]] = val
   219  	}
   220  	return bindings, nil
   221  }
   222  
   223  // Verb returns the verb part of the Pattern.
   224  func (p Pattern) Verb() string { return p.verb }
   225  
   226  func (p Pattern) String() string {
   227  	var stack []string
   228  	for _, op := range p.ops {
   229  		switch op.code {
   230  		case utilities.OpNop:
   231  			continue
   232  		case utilities.OpPush:
   233  			stack = append(stack, "*")
   234  		case utilities.OpLitPush:
   235  			stack = append(stack, p.pool[op.operand])
   236  		case utilities.OpPushM:
   237  			stack = append(stack, "**")
   238  		case utilities.OpConcatN:
   239  			n := op.operand
   240  			l := len(stack) - n
   241  			stack = append(stack[:l], strings.Join(stack[l:], "/"))
   242  		case utilities.OpCapture:
   243  			n := len(stack) - 1
   244  			stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n])
   245  		}
   246  	}
   247  	segs := strings.Join(stack, "/")
   248  	if p.verb != "" {
   249  		return fmt.Sprintf("/%s:%s", segs, p.verb)
   250  	}
   251  	return "/" + segs
   252  }