github.com/btccom/go-micro/v2@v2.9.3/api/router/util/runtime.go (about)

     1  package util
     2  
     3  // download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/runtime/pattern.go
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/btccom/go-micro/v2/logger"
    11  )
    12  
    13  var (
    14  	// ErrNotMatch indicates that the given HTTP request path does not match to the pattern.
    15  	ErrNotMatch = errors.New("not match to the path pattern")
    16  	// ErrInvalidPattern indicates that the given definition of Pattern is not valid.
    17  	ErrInvalidPattern = errors.New("invalid pattern")
    18  )
    19  
    20  type rop struct {
    21  	code    OpCode
    22  	operand int
    23  }
    24  
    25  // Pattern is a template pattern of http request paths defined in github.com/googleapis/googleapis/google/api/http.proto.
    26  type Pattern struct {
    27  	// ops is a list of operations
    28  	ops []rop
    29  	// pool is a constant pool indexed by the operands or vars.
    30  	pool []string
    31  	// vars is a list of variables names to be bound by this pattern
    32  	vars []string
    33  	// stacksize is the max depth of the stack
    34  	stacksize int
    35  	// tailLen is the length of the fixed-size segments after a deep wildcard
    36  	tailLen int
    37  	// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
    38  	verb string
    39  	// assumeColonVerb indicates whether a path suffix after a final
    40  	// colon may only be interpreted as a verb.
    41  	assumeColonVerb bool
    42  }
    43  
    44  type patternOptions struct {
    45  	assumeColonVerb bool
    46  }
    47  
    48  // PatternOpt is an option for creating Patterns.
    49  type PatternOpt func(*patternOptions)
    50  
    51  // NewPattern returns a new Pattern from the given definition values.
    52  // "ops" is a sequence of op codes. "pool" is a constant pool.
    53  // "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
    54  // "version" must be 1 for now.
    55  // It returns an error if the given definition is invalid.
    56  func NewPattern(version int, ops []int, pool []string, verb string, opts ...PatternOpt) (Pattern, error) {
    57  	options := patternOptions{
    58  		assumeColonVerb: true,
    59  	}
    60  	for _, o := range opts {
    61  		o(&options)
    62  	}
    63  
    64  	if version != 1 {
    65  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
    66  			logger.Debugf("unsupported version: %d", version)
    67  		}
    68  		return Pattern{}, ErrInvalidPattern
    69  	}
    70  
    71  	l := len(ops)
    72  	if l%2 != 0 {
    73  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
    74  			logger.Debugf("odd number of ops codes: %d", l)
    75  		}
    76  		return Pattern{}, ErrInvalidPattern
    77  	}
    78  
    79  	var (
    80  		typedOps        []rop
    81  		stack, maxstack int
    82  		tailLen         int
    83  		pushMSeen       bool
    84  		vars            []string
    85  	)
    86  	for i := 0; i < l; i += 2 {
    87  		op := rop{code: OpCode(ops[i]), operand: ops[i+1]}
    88  		switch op.code {
    89  		case OpNop:
    90  			continue
    91  		case OpPush:
    92  			if pushMSeen {
    93  				tailLen++
    94  			}
    95  			stack++
    96  		case OpPushM:
    97  			if pushMSeen {
    98  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
    99  					logger.Debug("pushM appears twice")
   100  				}
   101  				return Pattern{}, ErrInvalidPattern
   102  			}
   103  			pushMSeen = true
   104  			stack++
   105  		case OpLitPush:
   106  			if op.operand < 0 || len(pool) <= op.operand {
   107  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   108  					logger.Debugf("negative literal index: %d", op.operand)
   109  				}
   110  				return Pattern{}, ErrInvalidPattern
   111  			}
   112  			if pushMSeen {
   113  				tailLen++
   114  			}
   115  			stack++
   116  		case OpConcatN:
   117  			if op.operand <= 0 {
   118  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   119  					logger.Debugf("negative concat size: %d", op.operand)
   120  				}
   121  				return Pattern{}, ErrInvalidPattern
   122  			}
   123  			stack -= op.operand
   124  			if stack < 0 {
   125  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   126  					logger.Debug("stack underflow")
   127  				}
   128  				return Pattern{}, ErrInvalidPattern
   129  			}
   130  			stack++
   131  		case OpCapture:
   132  			if op.operand < 0 || len(pool) <= op.operand {
   133  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   134  					logger.Debugf("variable name index out of bound: %d", op.operand)
   135  				}
   136  				return Pattern{}, ErrInvalidPattern
   137  			}
   138  			v := pool[op.operand]
   139  			op.operand = len(vars)
   140  			vars = append(vars, v)
   141  			stack--
   142  			if stack < 0 {
   143  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   144  					logger.Debug("stack underflow")
   145  				}
   146  				return Pattern{}, ErrInvalidPattern
   147  			}
   148  		default:
   149  			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   150  				logger.Debugf("invalid opcode: %d", op.code)
   151  			}
   152  			return Pattern{}, ErrInvalidPattern
   153  		}
   154  
   155  		if maxstack < stack {
   156  			maxstack = stack
   157  		}
   158  		typedOps = append(typedOps, op)
   159  	}
   160  	return Pattern{
   161  		ops:             typedOps,
   162  		pool:            pool,
   163  		vars:            vars,
   164  		stacksize:       maxstack,
   165  		tailLen:         tailLen,
   166  		verb:            verb,
   167  		assumeColonVerb: options.assumeColonVerb,
   168  	}, nil
   169  }
   170  
   171  // MustPattern is a helper function which makes it easier to call NewPattern in variable initialization.
   172  func MustPattern(p Pattern, err error) Pattern {
   173  	if err != nil {
   174  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   175  			logger.Fatalf("Pattern initialization failed: %v", err)
   176  		}
   177  	}
   178  	return p
   179  }
   180  
   181  // Match examines components if it matches to the Pattern.
   182  // If it matches, the function returns a mapping from field paths to their captured values.
   183  // If otherwise, the function returns an error.
   184  func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
   185  	if p.verb != verb {
   186  		if p.assumeColonVerb || p.verb != "" {
   187  			return nil, ErrNotMatch
   188  		}
   189  		if len(components) == 0 {
   190  			components = []string{":" + verb}
   191  		} else {
   192  			components = append([]string{}, components...)
   193  			components[len(components)-1] += ":" + verb
   194  		}
   195  		verb = ""
   196  	}
   197  
   198  	var pos int
   199  	stack := make([]string, 0, p.stacksize)
   200  	captured := make([]string, len(p.vars))
   201  	l := len(components)
   202  	for _, op := range p.ops {
   203  		switch op.code {
   204  		case OpNop:
   205  			continue
   206  		case OpPush, OpLitPush:
   207  			if pos >= l {
   208  				return nil, ErrNotMatch
   209  			}
   210  			c := components[pos]
   211  			if op.code == OpLitPush {
   212  				if lit := p.pool[op.operand]; c != lit {
   213  					return nil, ErrNotMatch
   214  				}
   215  			}
   216  			stack = append(stack, c)
   217  			pos++
   218  		case OpPushM:
   219  			end := len(components)
   220  			if end < pos+p.tailLen {
   221  				return nil, ErrNotMatch
   222  			}
   223  			end -= p.tailLen
   224  			stack = append(stack, strings.Join(components[pos:end], "/"))
   225  			pos = end
   226  		case OpConcatN:
   227  			n := op.operand
   228  			l := len(stack) - n
   229  			stack = append(stack[:l], strings.Join(stack[l:], "/"))
   230  		case OpCapture:
   231  			n := len(stack) - 1
   232  			captured[op.operand] = stack[n]
   233  			stack = stack[:n]
   234  		}
   235  	}
   236  	if pos < l {
   237  		return nil, ErrNotMatch
   238  	}
   239  	bindings := make(map[string]string)
   240  	for i, val := range captured {
   241  		bindings[p.vars[i]] = val
   242  	}
   243  	return bindings, nil
   244  }
   245  
   246  // Verb returns the verb part of the Pattern.
   247  func (p Pattern) Verb() string { return p.verb }
   248  
   249  func (p Pattern) String() string {
   250  	var stack []string
   251  	for _, op := range p.ops {
   252  		switch op.code {
   253  		case OpNop:
   254  			continue
   255  		case OpPush:
   256  			stack = append(stack, "*")
   257  		case OpLitPush:
   258  			stack = append(stack, p.pool[op.operand])
   259  		case OpPushM:
   260  			stack = append(stack, "**")
   261  		case OpConcatN:
   262  			n := op.operand
   263  			l := len(stack) - n
   264  			stack = append(stack[:l], strings.Join(stack[l:], "/"))
   265  		case OpCapture:
   266  			n := len(stack) - 1
   267  			stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n])
   268  		}
   269  	}
   270  	segs := strings.Join(stack, "/")
   271  	if p.verb != "" {
   272  		return fmt.Sprintf("/%s:%s", segs, p.verb)
   273  	}
   274  	return "/" + segs
   275  }
   276  
   277  // AssumeColonVerbOpt indicates whether a path suffix after a final
   278  // colon may only be interpreted as a verb.
   279  func AssumeColonVerbOpt(val bool) PatternOpt {
   280  	return PatternOpt(func(o *patternOptions) {
   281  		o.assumeColonVerb = val
   282  	})
   283  }