blob: f16a84ad3892ed4a2e7948dc2989128b3ba0c7d0 [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001package runtime
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7
8 "github.com/grpc-ecosystem/grpc-gateway/utilities"
9 "google.golang.org/grpc/grpclog"
10)
11
12var (
13 // ErrNotMatch indicates that the given HTTP request path does not match to the pattern.
14 ErrNotMatch = errors.New("not match to the path pattern")
15 // ErrInvalidPattern indicates that the given definition of Pattern is not valid.
16 ErrInvalidPattern = errors.New("invalid pattern")
17)
18
19type op struct {
20 code utilities.OpCode
21 operand int
22}
23
24// Pattern is a template pattern of http request paths defined in github.com/googleapis/googleapis/google/api/http.proto.
25type Pattern struct {
26 // ops is a list of operations
27 ops []op
28 // pool is a constant pool indexed by the operands or vars.
29 pool []string
30 // vars is a list of variables names to be bound by this pattern
31 vars []string
32 // stacksize is the max depth of the stack
33 stacksize int
34 // tailLen is the length of the fixed-size segments after a deep wildcard
35 tailLen int
36 // verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
37 verb string
38}
39
40// NewPattern returns a new Pattern from the given definition values.
41// "ops" is a sequence of op codes. "pool" is a constant pool.
42// "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
43// "version" must be 1 for now.
44// It returns an error if the given definition is invalid.
45func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
46 if version != 1 {
47 grpclog.Infof("unsupported version: %d", version)
48 return Pattern{}, ErrInvalidPattern
49 }
50
51 l := len(ops)
52 if l%2 != 0 {
53 grpclog.Infof("odd number of ops codes: %d", l)
54 return Pattern{}, ErrInvalidPattern
55 }
56
57 var (
58 typedOps []op
59 stack, maxstack int
60 tailLen int
61 pushMSeen bool
62 vars []string
63 )
64 for i := 0; i < l; i += 2 {
65 op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]}
66 switch op.code {
67 case utilities.OpNop:
68 continue
69 case utilities.OpPush:
70 if pushMSeen {
71 tailLen++
72 }
73 stack++
74 case utilities.OpPushM:
75 if pushMSeen {
76 grpclog.Infof("pushM appears twice")
77 return Pattern{}, ErrInvalidPattern
78 }
79 pushMSeen = true
80 stack++
81 case utilities.OpLitPush:
82 if op.operand < 0 || len(pool) <= op.operand {
83 grpclog.Infof("negative literal index: %d", op.operand)
84 return Pattern{}, ErrInvalidPattern
85 }
86 if pushMSeen {
87 tailLen++
88 }
89 stack++
90 case utilities.OpConcatN:
91 if op.operand <= 0 {
92 grpclog.Infof("negative concat size: %d", op.operand)
93 return Pattern{}, ErrInvalidPattern
94 }
95 stack -= op.operand
96 if stack < 0 {
97 grpclog.Print("stack underflow")
98 return Pattern{}, ErrInvalidPattern
99 }
100 stack++
101 case utilities.OpCapture:
102 if op.operand < 0 || len(pool) <= op.operand {
103 grpclog.Infof("variable name index out of bound: %d", op.operand)
104 return Pattern{}, ErrInvalidPattern
105 }
106 v := pool[op.operand]
107 op.operand = len(vars)
108 vars = append(vars, v)
109 stack--
110 if stack < 0 {
111 grpclog.Infof("stack underflow")
112 return Pattern{}, ErrInvalidPattern
113 }
114 default:
115 grpclog.Infof("invalid opcode: %d", op.code)
116 return Pattern{}, ErrInvalidPattern
117 }
118
119 if maxstack < stack {
120 maxstack = stack
121 }
122 typedOps = append(typedOps, op)
123 }
124 return Pattern{
125 ops: typedOps,
126 pool: pool,
127 vars: vars,
128 stacksize: maxstack,
129 tailLen: tailLen,
130 verb: verb,
131 }, nil
132}
133
134// MustPattern is a helper function which makes it easier to call NewPattern in variable initialization.
135func MustPattern(p Pattern, err error) Pattern {
136 if err != nil {
137 grpclog.Fatalf("Pattern initialization failed: %v", err)
138 }
139 return p
140}
141
142// Match examines components if it matches to the Pattern.
143// If it matches, the function returns a mapping from field paths to their captured values.
144// If otherwise, the function returns an error.
145func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
146 if p.verb != verb {
147 return nil, ErrNotMatch
148 }
149
150 var pos int
151 stack := make([]string, 0, p.stacksize)
152 captured := make([]string, len(p.vars))
153 l := len(components)
154 for _, op := range p.ops {
155 switch op.code {
156 case utilities.OpNop:
157 continue
158 case utilities.OpPush, utilities.OpLitPush:
159 if pos >= l {
160 return nil, ErrNotMatch
161 }
162 c := components[pos]
163 if op.code == utilities.OpLitPush {
164 if lit := p.pool[op.operand]; c != lit {
165 return nil, ErrNotMatch
166 }
167 }
168 stack = append(stack, c)
169 pos++
170 case utilities.OpPushM:
171 end := len(components)
172 if end < pos+p.tailLen {
173 return nil, ErrNotMatch
174 }
175 end -= p.tailLen
176 stack = append(stack, strings.Join(components[pos:end], "/"))
177 pos = end
178 case utilities.OpConcatN:
179 n := op.operand
180 l := len(stack) - n
181 stack = append(stack[:l], strings.Join(stack[l:], "/"))
182 case utilities.OpCapture:
183 n := len(stack) - 1
184 captured[op.operand] = stack[n]
185 stack = stack[:n]
186 }
187 }
188 if pos < l {
189 return nil, ErrNotMatch
190 }
191 bindings := make(map[string]string)
192 for i, val := range captured {
193 bindings[p.vars[i]] = val
194 }
195 return bindings, nil
196}
197
198// Verb returns the verb part of the Pattern.
199func (p Pattern) Verb() string { return p.verb }
200
201func (p Pattern) String() string {
202 var stack []string
203 for _, op := range p.ops {
204 switch op.code {
205 case utilities.OpNop:
206 continue
207 case utilities.OpPush:
208 stack = append(stack, "*")
209 case utilities.OpLitPush:
210 stack = append(stack, p.pool[op.operand])
211 case utilities.OpPushM:
212 stack = append(stack, "**")
213 case utilities.OpConcatN:
214 n := op.operand
215 l := len(stack) - n
216 stack = append(stack[:l], strings.Join(stack[l:], "/"))
217 case utilities.OpCapture:
218 n := len(stack) - 1
219 stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n])
220 }
221 }
222 segs := strings.Join(stack, "/")
223 if p.verb != "" {
224 return fmt.Sprintf("/%s:%s", segs, p.verb)
225 }
226 return "/" + segs
227}