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 }