github.com/btccom/go-micro/v2@v2.9.3/api/router/util/parse.go (about) 1 package util 2 3 // download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/parse.go 4 5 import ( 6 "fmt" 7 "strings" 8 9 "github.com/btccom/go-micro/v2/logger" 10 ) 11 12 // InvalidTemplateError indicates that the path template is not valid. 13 type InvalidTemplateError struct { 14 tmpl string 15 msg string 16 } 17 18 func (e InvalidTemplateError) Error() string { 19 return fmt.Sprintf("%s: %s", e.msg, e.tmpl) 20 } 21 22 // Parse parses the string representation of path template 23 func Parse(tmpl string) (Compiler, error) { 24 if !strings.HasPrefix(tmpl, "/") { 25 return template{}, InvalidTemplateError{tmpl: tmpl, msg: "no leading /"} 26 } 27 tokens, verb := tokenize(tmpl[1:]) 28 29 p := parser{tokens: tokens} 30 segs, err := p.topLevelSegments() 31 if err != nil { 32 return template{}, InvalidTemplateError{tmpl: tmpl, msg: err.Error()} 33 } 34 35 return template{ 36 segments: segs, 37 verb: verb, 38 template: tmpl, 39 }, nil 40 } 41 42 func tokenize(path string) (tokens []string, verb string) { 43 if path == "" { 44 return []string{eof}, "" 45 } 46 47 const ( 48 init = iota 49 field 50 nested 51 ) 52 var ( 53 st = init 54 ) 55 for path != "" { 56 var idx int 57 switch st { 58 case init: 59 idx = strings.IndexAny(path, "/{") 60 case field: 61 idx = strings.IndexAny(path, ".=}") 62 case nested: 63 idx = strings.IndexAny(path, "/}") 64 } 65 if idx < 0 { 66 tokens = append(tokens, path) 67 break 68 } 69 switch r := path[idx]; r { 70 case '/', '.': 71 case '{': 72 st = field 73 case '=': 74 st = nested 75 case '}': 76 st = init 77 } 78 if idx == 0 { 79 tokens = append(tokens, path[idx:idx+1]) 80 } else { 81 tokens = append(tokens, path[:idx], path[idx:idx+1]) 82 } 83 path = path[idx+1:] 84 } 85 86 l := len(tokens) 87 t := tokens[l-1] 88 if idx := strings.LastIndex(t, ":"); idx == 0 { 89 tokens, verb = tokens[:l-1], t[1:] 90 } else if idx > 0 { 91 tokens[l-1], verb = t[:idx], t[idx+1:] 92 } 93 tokens = append(tokens, eof) 94 return tokens, verb 95 } 96 97 // parser is a parser of the template syntax defined in github.com/googleapis/googleapis/google/api/http.proto. 98 type parser struct { 99 tokens []string 100 accepted []string 101 } 102 103 // topLevelSegments is the target of this parser. 104 func (p *parser) topLevelSegments() ([]segment, error) { 105 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 106 logger.Debugf("Parsing %q", p.tokens) 107 } 108 segs, err := p.segments() 109 if err != nil { 110 return nil, err 111 } 112 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 113 logger.Debugf("accept segments: %q; %q", p.accepted, p.tokens) 114 } 115 if _, err := p.accept(typeEOF); err != nil { 116 return nil, fmt.Errorf("unexpected token %q after segments %q", p.tokens[0], strings.Join(p.accepted, "")) 117 } 118 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 119 logger.Debugf("accept eof: %q; %q", p.accepted, p.tokens) 120 } 121 return segs, nil 122 } 123 124 func (p *parser) segments() ([]segment, error) { 125 s, err := p.segment() 126 if err != nil { 127 return nil, err 128 } 129 130 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 131 logger.Debugf("accept segment: %q; %q", p.accepted, p.tokens) 132 } 133 segs := []segment{s} 134 for { 135 if _, err := p.accept("/"); err != nil { 136 return segs, nil 137 } 138 s, err := p.segment() 139 if err != nil { 140 return segs, err 141 } 142 segs = append(segs, s) 143 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 144 logger.Debugf("accept segment: %q; %q", p.accepted, p.tokens) 145 } 146 } 147 } 148 149 func (p *parser) segment() (segment, error) { 150 if _, err := p.accept("*"); err == nil { 151 return wildcard{}, nil 152 } 153 if _, err := p.accept("**"); err == nil { 154 return deepWildcard{}, nil 155 } 156 if l, err := p.literal(); err == nil { 157 return l, nil 158 } 159 160 v, err := p.variable() 161 if err != nil { 162 return nil, fmt.Errorf("segment neither wildcards, literal or variable: %v", err) 163 } 164 return v, err 165 } 166 167 func (p *parser) literal() (segment, error) { 168 lit, err := p.accept(typeLiteral) 169 if err != nil { 170 return nil, err 171 } 172 return literal(lit), nil 173 } 174 175 func (p *parser) variable() (segment, error) { 176 if _, err := p.accept("{"); err != nil { 177 return nil, err 178 } 179 180 path, err := p.fieldPath() 181 if err != nil { 182 return nil, err 183 } 184 185 var segs []segment 186 if _, err := p.accept("="); err == nil { 187 segs, err = p.segments() 188 if err != nil { 189 return nil, fmt.Errorf("invalid segment in variable %q: %v", path, err) 190 } 191 } else { 192 segs = []segment{wildcard{}} 193 } 194 195 if _, err := p.accept("}"); err != nil { 196 return nil, fmt.Errorf("unterminated variable segment: %s", path) 197 } 198 return variable{ 199 path: path, 200 segments: segs, 201 }, nil 202 } 203 204 func (p *parser) fieldPath() (string, error) { 205 c, err := p.accept(typeIdent) 206 if err != nil { 207 return "", err 208 } 209 components := []string{c} 210 for { 211 if _, err = p.accept("."); err != nil { 212 return strings.Join(components, "."), nil 213 } 214 c, err := p.accept(typeIdent) 215 if err != nil { 216 return "", fmt.Errorf("invalid field path component: %v", err) 217 } 218 components = append(components, c) 219 } 220 } 221 222 // A termType is a type of terminal symbols. 223 type termType string 224 225 // These constants define some of valid values of termType. 226 // They improve readability of parse functions. 227 // 228 // You can also use "/", "*", "**", "." or "=" as valid values. 229 const ( 230 typeIdent = termType("ident") 231 typeLiteral = termType("literal") 232 typeEOF = termType("$") 233 ) 234 235 const ( 236 // eof is the terminal symbol which always appears at the end of token sequence. 237 eof = "\u0000" 238 ) 239 240 // accept tries to accept a token in "p". 241 // This function consumes a token and returns it if it matches to the specified "term". 242 // If it doesn't match, the function does not consume any tokens and return an error. 243 func (p *parser) accept(term termType) (string, error) { 244 t := p.tokens[0] 245 switch term { 246 case "/", "*", "**", ".", "=", "{", "}": 247 if t != string(term) && t != "/" { 248 return "", fmt.Errorf("expected %q but got %q", term, t) 249 } 250 case typeEOF: 251 if t != eof { 252 return "", fmt.Errorf("expected EOF but got %q", t) 253 } 254 case typeIdent: 255 if err := expectIdent(t); err != nil { 256 return "", err 257 } 258 case typeLiteral: 259 if err := expectPChars(t); err != nil { 260 return "", err 261 } 262 default: 263 return "", fmt.Errorf("unknown termType %q", term) 264 } 265 p.tokens = p.tokens[1:] 266 p.accepted = append(p.accepted, t) 267 return t, nil 268 } 269 270 // expectPChars determines if "t" consists of only pchars defined in RFC3986. 271 // 272 // https://www.ietf.org/rfc/rfc3986.txt, P.49 273 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 274 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 275 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 276 // / "*" / "+" / "," / ";" / "=" 277 // pct-encoded = "%" HEXDIG HEXDIG 278 func expectPChars(t string) error { 279 const ( 280 init = iota 281 pct1 282 pct2 283 ) 284 st := init 285 for _, r := range t { 286 if st != init { 287 if !isHexDigit(r) { 288 return fmt.Errorf("invalid hexdigit: %c(%U)", r, r) 289 } 290 switch st { 291 case pct1: 292 st = pct2 293 case pct2: 294 st = init 295 } 296 continue 297 } 298 299 // unreserved 300 switch { 301 case 'A' <= r && r <= 'Z': 302 continue 303 case 'a' <= r && r <= 'z': 304 continue 305 case '0' <= r && r <= '9': 306 continue 307 } 308 switch r { 309 case '-', '.', '_', '~': 310 // unreserved 311 case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': 312 // sub-delims 313 case ':', '@': 314 // rest of pchar 315 case '%': 316 // pct-encoded 317 st = pct1 318 default: 319 return fmt.Errorf("invalid character in path segment: %q(%U)", r, r) 320 } 321 } 322 if st != init { 323 return fmt.Errorf("invalid percent-encoding in %q", t) 324 } 325 return nil 326 } 327 328 // expectIdent determines if "ident" is a valid identifier in .proto schema ([[:alpha:]_][[:alphanum:]_]*). 329 func expectIdent(ident string) error { 330 if ident == "" { 331 return fmt.Errorf("empty identifier") 332 } 333 for pos, r := range ident { 334 switch { 335 case '0' <= r && r <= '9': 336 if pos == 0 { 337 return fmt.Errorf("identifier starting with digit: %s", ident) 338 } 339 continue 340 case 'A' <= r && r <= 'Z': 341 continue 342 case 'a' <= r && r <= 'z': 343 continue 344 case r == '_': 345 continue 346 default: 347 return fmt.Errorf("invalid character %q(%U) in identifier: %s", r, r, ident) 348 } 349 } 350 return nil 351 } 352 353 func isHexDigit(r rune) bool { 354 switch { 355 case '0' <= r && r <= '9': 356 return true 357 case 'A' <= r && r <= 'F': 358 return true 359 case 'a' <= r && r <= 'f': 360 return true 361 } 362 return false 363 }