| package gax |
| |
| import ( |
| "fmt" |
| "io" |
| "strings" |
| ) |
| |
| // This parser follows the syntax of path templates, from |
| // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto. |
| // The differences are that there is no custom verb, we allow the initial slash |
| // to be absent, and that we are not strict as |
| // https://tools.ietf.org/html/rfc6570 about the characters in identifiers and |
| // literals. |
| |
| type pathTemplateParser struct { |
| r *strings.Reader |
| runeCount int // the number of the current rune in the original string |
| nextVar int // the number to use for the next unnamed variable |
| seenName map[string]bool // names we've seen already |
| seenPathWildcard bool // have we seen "**" already? |
| } |
| |
| func parsePathTemplate(template string) (pt *PathTemplate, err error) { |
| p := &pathTemplateParser{ |
| r: strings.NewReader(template), |
| seenName: map[string]bool{}, |
| } |
| |
| // Handle panics with strings like errors. |
| // See pathTemplateParser.error, below. |
| defer func() { |
| if x := recover(); x != nil { |
| errmsg, ok := x.(errString) |
| if !ok { |
| panic(x) |
| } |
| pt = nil |
| err = ParseError{p.runeCount, template, string(errmsg)} |
| } |
| }() |
| |
| segs := p.template() |
| // If there is a path wildcard, set its length. We can't do this |
| // until we know how many segments we've got all together. |
| for i, seg := range segs { |
| if _, ok := seg.matcher.(pathWildcardMatcher); ok { |
| segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1) |
| break |
| } |
| } |
| return &PathTemplate{segments: segs}, nil |
| |
| } |
| |
| // Used to indicate errors "thrown" by this parser. We don't use string because |
| // many parts of the standard library panic with strings. |
| type errString string |
| |
| // Terminates parsing immediately with an error. |
| func (p *pathTemplateParser) error(msg string) { |
| panic(errString(msg)) |
| } |
| |
| // Template = [ "/" ] Segments |
| func (p *pathTemplateParser) template() []segment { |
| var segs []segment |
| if p.consume('/') { |
| // Initial '/' needs an initial empty matcher. |
| segs = append(segs, segment{matcher: labelMatcher("")}) |
| } |
| return append(segs, p.segments("")...) |
| } |
| |
| // Segments = Segment { "/" Segment } |
| func (p *pathTemplateParser) segments(name string) []segment { |
| var segs []segment |
| for { |
| subsegs := p.segment(name) |
| segs = append(segs, subsegs...) |
| if !p.consume('/') { |
| break |
| } |
| } |
| return segs |
| } |
| |
| // Segment = "*" | "**" | LITERAL | Variable |
| func (p *pathTemplateParser) segment(name string) []segment { |
| if p.consume('*') { |
| if name == "" { |
| name = fmt.Sprintf("$%d", p.nextVar) |
| p.nextVar++ |
| } |
| if p.consume('*') { |
| if p.seenPathWildcard { |
| p.error("multiple '**' disallowed") |
| } |
| p.seenPathWildcard = true |
| // We'll change 0 to the right number at the end. |
| return []segment{{name: name, matcher: pathWildcardMatcher(0)}} |
| } |
| return []segment{{name: name, matcher: wildcardMatcher(0)}} |
| } |
| if p.consume('{') { |
| if name != "" { |
| p.error("recursive named bindings are not allowed") |
| } |
| return p.variable() |
| } |
| return []segment{{name: name, matcher: labelMatcher(p.literal())}} |
| } |
| |
| // Variable = "{" FieldPath [ "=" Segments ] "}" |
| // "{" is already consumed. |
| func (p *pathTemplateParser) variable() []segment { |
| // Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT } |
| name := p.literal() |
| if p.seenName[name] { |
| p.error(name + " appears multiple times") |
| } |
| p.seenName[name] = true |
| var segs []segment |
| if p.consume('=') { |
| segs = p.segments(name) |
| } else { |
| // "{var}" is equivalent to "{var=*}" |
| segs = []segment{{name: name, matcher: wildcardMatcher(0)}} |
| } |
| if !p.consume('}') { |
| p.error("expected '}'") |
| } |
| return segs |
| } |
| |
| // A literal is any sequence of characters other than a few special ones. |
| // The list of stop characters is not quite the same as in the template RFC. |
| func (p *pathTemplateParser) literal() string { |
| lit := p.consumeUntil("/*}{=") |
| if lit == "" { |
| p.error("empty literal") |
| } |
| return lit |
| } |
| |
| // Read runes until EOF or one of the runes in stopRunes is encountered. |
| // If the latter, unread the stop rune. Return the accumulated runes as a string. |
| func (p *pathTemplateParser) consumeUntil(stopRunes string) string { |
| var runes []rune |
| for { |
| r, ok := p.readRune() |
| if !ok { |
| break |
| } |
| if strings.IndexRune(stopRunes, r) >= 0 { |
| p.unreadRune() |
| break |
| } |
| runes = append(runes, r) |
| } |
| return string(runes) |
| } |
| |
| // If the next rune is r, consume it and return true. |
| // Otherwise, leave the input unchanged and return false. |
| func (p *pathTemplateParser) consume(r rune) bool { |
| rr, ok := p.readRune() |
| if !ok { |
| return false |
| } |
| if r == rr { |
| return true |
| } |
| p.unreadRune() |
| return false |
| } |
| |
| // Read the next rune from the input. Return it. |
| // The second return value is false at EOF. |
| func (p *pathTemplateParser) readRune() (rune, bool) { |
| r, _, err := p.r.ReadRune() |
| if err == io.EOF { |
| return r, false |
| } |
| if err != nil { |
| p.error(err.Error()) |
| } |
| p.runeCount++ |
| return r, true |
| } |
| |
| // Put the last rune that was read back on the input. |
| func (p *pathTemplateParser) unreadRune() { |
| if err := p.r.UnreadRune(); err != nil { |
| p.error(err.Error()) |
| } |
| p.runeCount-- |
| } |