| // Copyright 2016, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| 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-- |
| } |