Codebase list golang-github-googleapis-gax-go / 033555a4-5937-447a-81bc-b1bac4f94abe/v1.0.0
Revert "delete path template (#49)" (#50) This reverts commit 2cadd475a3e966ec9b77a21afc530dbacec6d613. Michael Darakananda authored 6 years ago GitHub committed 6 years ago
4 changed file(s) with 615 addition(s) and 1 deletion(s). Raw diff Collapse all Expand all
3636 // This project is currently experimental and not supported.
3737 package gax
3838
39 const Version = "0.2.0"
39 const Version = "0.1.0"
0 // Copyright 2016, Google Inc.
1 // All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 // * Neither the name of Google Inc. nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 package gax
30
31 import (
32 "errors"
33 "fmt"
34 "strings"
35 )
36
37 type matcher interface {
38 match([]string) (int, error)
39 String() string
40 }
41
42 type segment struct {
43 matcher
44 name string
45 }
46
47 type labelMatcher string
48
49 func (ls labelMatcher) match(segments []string) (int, error) {
50 if len(segments) == 0 {
51 return 0, fmt.Errorf("expected %s but no more segments found", ls)
52 }
53 if segments[0] != string(ls) {
54 return 0, fmt.Errorf("expected %s but got %s", ls, segments[0])
55 }
56 return 1, nil
57 }
58
59 func (ls labelMatcher) String() string {
60 return string(ls)
61 }
62
63 type wildcardMatcher int
64
65 func (wm wildcardMatcher) match(segments []string) (int, error) {
66 if len(segments) == 0 {
67 return 0, errors.New("no more segments found")
68 }
69 return 1, nil
70 }
71
72 func (wm wildcardMatcher) String() string {
73 return "*"
74 }
75
76 type pathWildcardMatcher int
77
78 func (pwm pathWildcardMatcher) match(segments []string) (int, error) {
79 length := len(segments) - int(pwm)
80 if length <= 0 {
81 return 0, errors.New("not sufficient segments are supplied for path wildcard")
82 }
83 return length, nil
84 }
85
86 func (pwm pathWildcardMatcher) String() string {
87 return "**"
88 }
89
90 type ParseError struct {
91 Pos int
92 Template string
93 Message string
94 }
95
96 func (pe ParseError) Error() string {
97 return fmt.Sprintf("at %d of template '%s', %s", pe.Pos, pe.Template, pe.Message)
98 }
99
100 // PathTemplate manages the template to build and match with paths used
101 // by API services. It holds a template and variable names in it, and
102 // it can extract matched patterns from a path string or build a path
103 // string from a binding.
104 //
105 // See http.proto in github.com/googleapis/googleapis/ for the details of
106 // the template syntax.
107 type PathTemplate struct {
108 segments []segment
109 }
110
111 // NewPathTemplate parses a path template, and returns a PathTemplate
112 // instance if successful.
113 func NewPathTemplate(template string) (*PathTemplate, error) {
114 return parsePathTemplate(template)
115 }
116
117 // MustCompilePathTemplate is like NewPathTemplate but panics if the
118 // expression cannot be parsed. It simplifies safe initialization of
119 // global variables holding compiled regular expressions.
120 func MustCompilePathTemplate(template string) *PathTemplate {
121 pt, err := NewPathTemplate(template)
122 if err != nil {
123 panic(err)
124 }
125 return pt
126 }
127
128 // Match attempts to match the given path with the template, and returns
129 // the mapping of the variable name to the matched pattern string.
130 func (pt *PathTemplate) Match(path string) (map[string]string, error) {
131 paths := strings.Split(path, "/")
132 values := map[string]string{}
133 for _, segment := range pt.segments {
134 length, err := segment.match(paths)
135 if err != nil {
136 return nil, err
137 }
138 if segment.name != "" {
139 value := strings.Join(paths[:length], "/")
140 if oldValue, ok := values[segment.name]; ok {
141 values[segment.name] = oldValue + "/" + value
142 } else {
143 values[segment.name] = value
144 }
145 }
146 paths = paths[length:]
147 }
148 if len(paths) != 0 {
149 return nil, fmt.Errorf("Trailing path %s remains after the matching", strings.Join(paths, "/"))
150 }
151 return values, nil
152 }
153
154 // Render creates a path string from its template and the binding from
155 // the variable name to the value.
156 func (pt *PathTemplate) Render(binding map[string]string) (string, error) {
157 result := make([]string, 0, len(pt.segments))
158 var lastVariableName string
159 for _, segment := range pt.segments {
160 name := segment.name
161 if lastVariableName != "" && name == lastVariableName {
162 continue
163 }
164 lastVariableName = name
165 if name == "" {
166 result = append(result, segment.String())
167 } else if value, ok := binding[name]; ok {
168 result = append(result, value)
169 } else {
170 return "", fmt.Errorf("%s is not found", name)
171 }
172 }
173 built := strings.Join(result, "/")
174 return built, nil
175 }
0 // Copyright 2016, Google Inc.
1 // All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 // * Neither the name of Google Inc. nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 package gax
30
31 import (
32 "fmt"
33 "io"
34 "strings"
35 )
36
37 // This parser follows the syntax of path templates, from
38 // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto.
39 // The differences are that there is no custom verb, we allow the initial slash
40 // to be absent, and that we are not strict as
41 // https://tools.ietf.org/html/rfc6570 about the characters in identifiers and
42 // literals.
43
44 type pathTemplateParser struct {
45 r *strings.Reader
46 runeCount int // the number of the current rune in the original string
47 nextVar int // the number to use for the next unnamed variable
48 seenName map[string]bool // names we've seen already
49 seenPathWildcard bool // have we seen "**" already?
50 }
51
52 func parsePathTemplate(template string) (pt *PathTemplate, err error) {
53 p := &pathTemplateParser{
54 r: strings.NewReader(template),
55 seenName: map[string]bool{},
56 }
57
58 // Handle panics with strings like errors.
59 // See pathTemplateParser.error, below.
60 defer func() {
61 if x := recover(); x != nil {
62 errmsg, ok := x.(errString)
63 if !ok {
64 panic(x)
65 }
66 pt = nil
67 err = ParseError{p.runeCount, template, string(errmsg)}
68 }
69 }()
70
71 segs := p.template()
72 // If there is a path wildcard, set its length. We can't do this
73 // until we know how many segments we've got all together.
74 for i, seg := range segs {
75 if _, ok := seg.matcher.(pathWildcardMatcher); ok {
76 segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1)
77 break
78 }
79 }
80 return &PathTemplate{segments: segs}, nil
81
82 }
83
84 // Used to indicate errors "thrown" by this parser. We don't use string because
85 // many parts of the standard library panic with strings.
86 type errString string
87
88 // Terminates parsing immediately with an error.
89 func (p *pathTemplateParser) error(msg string) {
90 panic(errString(msg))
91 }
92
93 // Template = [ "/" ] Segments
94 func (p *pathTemplateParser) template() []segment {
95 var segs []segment
96 if p.consume('/') {
97 // Initial '/' needs an initial empty matcher.
98 segs = append(segs, segment{matcher: labelMatcher("")})
99 }
100 return append(segs, p.segments("")...)
101 }
102
103 // Segments = Segment { "/" Segment }
104 func (p *pathTemplateParser) segments(name string) []segment {
105 var segs []segment
106 for {
107 subsegs := p.segment(name)
108 segs = append(segs, subsegs...)
109 if !p.consume('/') {
110 break
111 }
112 }
113 return segs
114 }
115
116 // Segment = "*" | "**" | LITERAL | Variable
117 func (p *pathTemplateParser) segment(name string) []segment {
118 if p.consume('*') {
119 if name == "" {
120 name = fmt.Sprintf("$%d", p.nextVar)
121 p.nextVar++
122 }
123 if p.consume('*') {
124 if p.seenPathWildcard {
125 p.error("multiple '**' disallowed")
126 }
127 p.seenPathWildcard = true
128 // We'll change 0 to the right number at the end.
129 return []segment{{name: name, matcher: pathWildcardMatcher(0)}}
130 }
131 return []segment{{name: name, matcher: wildcardMatcher(0)}}
132 }
133 if p.consume('{') {
134 if name != "" {
135 p.error("recursive named bindings are not allowed")
136 }
137 return p.variable()
138 }
139 return []segment{{name: name, matcher: labelMatcher(p.literal())}}
140 }
141
142 // Variable = "{" FieldPath [ "=" Segments ] "}"
143 // "{" is already consumed.
144 func (p *pathTemplateParser) variable() []segment {
145 // Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT }
146 name := p.literal()
147 if p.seenName[name] {
148 p.error(name + " appears multiple times")
149 }
150 p.seenName[name] = true
151 var segs []segment
152 if p.consume('=') {
153 segs = p.segments(name)
154 } else {
155 // "{var}" is equivalent to "{var=*}"
156 segs = []segment{{name: name, matcher: wildcardMatcher(0)}}
157 }
158 if !p.consume('}') {
159 p.error("expected '}'")
160 }
161 return segs
162 }
163
164 // A literal is any sequence of characters other than a few special ones.
165 // The list of stop characters is not quite the same as in the template RFC.
166 func (p *pathTemplateParser) literal() string {
167 lit := p.consumeUntil("/*}{=")
168 if lit == "" {
169 p.error("empty literal")
170 }
171 return lit
172 }
173
174 // Read runes until EOF or one of the runes in stopRunes is encountered.
175 // If the latter, unread the stop rune. Return the accumulated runes as a string.
176 func (p *pathTemplateParser) consumeUntil(stopRunes string) string {
177 var runes []rune
178 for {
179 r, ok := p.readRune()
180 if !ok {
181 break
182 }
183 if strings.IndexRune(stopRunes, r) >= 0 {
184 p.unreadRune()
185 break
186 }
187 runes = append(runes, r)
188 }
189 return string(runes)
190 }
191
192 // If the next rune is r, consume it and return true.
193 // Otherwise, leave the input unchanged and return false.
194 func (p *pathTemplateParser) consume(r rune) bool {
195 rr, ok := p.readRune()
196 if !ok {
197 return false
198 }
199 if r == rr {
200 return true
201 }
202 p.unreadRune()
203 return false
204 }
205
206 // Read the next rune from the input. Return it.
207 // The second return value is false at EOF.
208 func (p *pathTemplateParser) readRune() (rune, bool) {
209 r, _, err := p.r.ReadRune()
210 if err == io.EOF {
211 return r, false
212 }
213 if err != nil {
214 p.error(err.Error())
215 }
216 p.runeCount++
217 return r, true
218 }
219
220 // Put the last rune that was read back on the input.
221 func (p *pathTemplateParser) unreadRune() {
222 if err := p.r.UnreadRune(); err != nil {
223 p.error(err.Error())
224 }
225 p.runeCount--
226 }
0 // Copyright 2016, Google Inc.
1 // All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 // * Neither the name of Google Inc. nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 package gax
30
31 import "testing"
32
33 func TestPathTemplateMatchRender(t *testing.T) {
34 testCases := []struct {
35 message string
36 template string
37 path string
38 values map[string]string
39 }{
40 {
41 "base",
42 "buckets/*/*/objects/*",
43 "buckets/f/o/objects/bar",
44 map[string]string{"$0": "f", "$1": "o", "$2": "bar"},
45 },
46 {
47 "path wildcards",
48 "bar/**/foo/*",
49 "bar/foo/foo/foo/bar",
50 map[string]string{"$0": "foo/foo", "$1": "bar"},
51 },
52 {
53 "named binding",
54 "buckets/{foo}/objects/*",
55 "buckets/foo/objects/bar",
56 map[string]string{"$0": "bar", "foo": "foo"},
57 },
58 {
59 "named binding with colon",
60 "buckets/{foo}/objects/*",
61 "buckets/foo:boo/objects/bar",
62 map[string]string{"$0": "bar", "foo": "foo:boo"},
63 },
64 {
65 "named binding with complex patterns",
66 "buckets/{foo=x/*/y/**}/objects/*",
67 "buckets/x/foo/y/bar/baz/objects/quox",
68 map[string]string{"$0": "quox", "foo": "x/foo/y/bar/baz"},
69 },
70 {
71 "starts with slash",
72 "/foo/*",
73 "/foo/bar",
74 map[string]string{"$0": "bar"},
75 },
76 }
77 for _, testCase := range testCases {
78 pt, err := NewPathTemplate(testCase.template)
79 if err != nil {
80 t.Errorf("[%s] Failed to parse template %s: %v", testCase.message, testCase.template, err)
81 continue
82 }
83 values, err := pt.Match(testCase.path)
84 if err != nil {
85 t.Errorf("[%s] PathTemplate '%s' failed to match with '%s', %v", testCase.message, testCase.template, testCase.path, err)
86 continue
87 }
88 for key, expected := range testCase.values {
89 actual, ok := values[key]
90 if !ok {
91 t.Errorf("[%s] The matched data misses the value for %s", testCase.message, key)
92 continue
93 }
94 delete(values, key)
95 if actual != expected {
96 t.Errorf("[%s] Failed to match: value for '%s' is expected '%s' but is actually '%s'", testCase.message, key, expected, actual)
97 }
98 }
99 if len(values) != 0 {
100 t.Errorf("[%s] The matched data has unexpected keys: %v", testCase.message, values)
101 }
102 built, err := pt.Render(testCase.values)
103 if err != nil || built != testCase.path {
104 t.Errorf("[%s] Built path '%s' is different from the expected '%s', %v", testCase.message, built, testCase.path, err)
105 }
106 }
107 }
108
109 func TestPathTemplateMatchFailure(t *testing.T) {
110 testCases := []struct {
111 message string
112 template string
113 path string
114 }{
115 {
116 "too many paths",
117 "buckets/*/*/objects/*",
118 "buckets/f/o/o/objects/bar",
119 },
120 {
121 "missing last path",
122 "buckets/*/*/objects/*",
123 "buckets/f/o/objects",
124 },
125 {
126 "too many paths at end",
127 "buckets/*/*/objects/*",
128 "buckets/f/o/objects/too/long",
129 },
130 }
131 for _, testCase := range testCases {
132 pt, err := NewPathTemplate(testCase.template)
133 if err != nil {
134 t.Errorf("[%s] Failed to parse path %s: %v", testCase.message, testCase.template, err)
135 continue
136 }
137 if values, err := pt.Match(testCase.path); err == nil {
138 t.Errorf("[%s] PathTemplate %s doesn't expect to match %s, but succeeded somehow. Match result: %v", testCase.message, testCase.template, testCase.path, values)
139
140 }
141 }
142 }
143
144 func TestPathTemplateRenderTooManyValues(t *testing.T) {
145 // Test cases where Render() succeeds but Match() doesn't return the same map.
146 testCases := []struct {
147 message string
148 template string
149 values map[string]string
150 expected string
151 }{
152 {
153 "too many",
154 "bar/*/foo/*",
155 map[string]string{"$0": "_1", "$1": "_2", "$2": "_3"},
156 "bar/_1/foo/_2",
157 },
158 }
159 for _, testCase := range testCases {
160 pt, err := NewPathTemplate(testCase.template)
161 if err != nil {
162 t.Errorf("[%s] Failed to parse template %s (error %v)", testCase.message, testCase.template, err)
163 continue
164 }
165 if result, err := pt.Render(testCase.values); err != nil || result != testCase.expected {
166 t.Errorf("[%s] Failed to build the path (expected '%s' but returned '%s'", testCase.message, testCase.expected, result)
167 }
168 }
169 }
170
171 func TestPathTemplateParseErrors(t *testing.T) {
172 testCases := []struct {
173 message string
174 template string
175 }{
176 {
177 "multiple path wildcards",
178 "foo/**/bar/**",
179 },
180 {
181 "recursive named bindings",
182 "foo/{foo=foo/{bar}/baz/*}/baz/*",
183 },
184 {
185 "complicated multiple path wildcards patterns",
186 "foo/{foo=foo/**/bar/*}/baz/**",
187 },
188 {
189 "consective slashes",
190 "foo//bar",
191 },
192 {
193 "invalid variable pattern",
194 "foo/{foo=foo/*/}bar",
195 },
196 {
197 "same name multiple times",
198 "foo/{foo}/bar/{foo}",
199 },
200 {
201 "empty string after '='",
202 "foo/{foo=}/bar",
203 },
204 }
205 for _, testCase := range testCases {
206 if pt, err := NewPathTemplate(testCase.template); err == nil {
207 t.Errorf("[%s] Template '%s' should fail to be parsed, but succeeded and returned %+v", testCase.message, testCase.template, pt)
208 }
209 }
210 }