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  }