-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrout_util.go
More file actions
276 lines (232 loc) · 4.95 KB
/
rout_util.go
File metadata and controls
276 lines (232 loc) · 4.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package rout
import (
"errors"
"fmt"
r "reflect"
"regexp"
"strings"
"sync"
u "unsafe"
)
const (
segmentPattern = `([^/?#]+)`
segmentTemplate = `{}`
subsCap = 8
)
var regexpCache sync.Map
// Susceptible to "thundering herd" but probably good enough.
func cachedRegexp(pattern string) *regexp.Regexp {
val, ok := regexpCache.Load(pattern)
if ok {
return val.(*regexp.Regexp)
}
reg := regexp.MustCompile(pattern)
regexpCache.Store(pattern, reg)
return reg
}
var patCache sync.Map
// Susceptible to "thundering herd" but probably good enough.
func cachedPat(pattern string) Pat {
val, ok := patCache.Load(pattern)
if ok {
return val.(Pat)
}
var pat Pat
try(pat.Parse(pattern))
patCache.Store(pattern, pat)
return pat
}
func try(err error) {
if err != nil {
panic(err)
}
}
func rec(ptr *error) {
err := toErr(recover())
if err != nil {
*ptr = err
}
}
func toErr(val interface{}) error {
if val == nil {
return nil
}
err, _ := val.(error)
if err != nil {
return err
}
return nonErr{val}
}
type nonErr [1]interface{}
func (self nonErr) Error() string {
if self[0] != nil {
return fmt.Sprint(self[0])
}
return ``
}
func hasSlashPrefix(val string) bool {
return len(val) > 0 && val[0] == '/'
}
func hasSlashSuffix(val string) bool {
return len(val) > 0 && val[len(val)-1] == '/'
}
func errStatusDeep(err error) int {
for err != nil {
impl, _ := err.(interface{ HttpStatusCode() int })
if impl != nil {
return impl.HttpStatusCode()
}
err = errUnwrap(err)
}
return 0
}
/*
Improved version of `errors.Unwrap` which returns nil if the error incorrectly
unwraps to itself, to avoid an infinite loop.
*/
func errUnwrap(err error) error {
cause := errors.Unwrap(err)
if cause == nil || r.DeepEqual(err, cause) {
return nil
}
return cause
}
func intLen(val int) (count int) {
if val < 0 {
count++
}
for {
count++
val /= 10
if val == 0 {
break
}
}
return
}
/*
Allocation-free conversion. Reinterprets a byte slice as a string. Borrowed from
the standard library. Reasonably safe. Should not be used when the underlying
byte array is volatile, for example part of a scratch buffer in SQL scanning.
*/
func bytesString(val []byte) string {
return *(*string)(u.Pointer(&val))
}
// TODO consider caching.
func exaToReg(src string) string {
return `^` + regexp.QuoteMeta(src) + `$`
}
// TODO consider caching.
func staToReg(src string) string {
return `^` + regexp.QuoteMeta(src)
}
// TODO consider caching.
func patToReg(src string) string {
return cachedPat(src).Reg()
}
/*
AFAIK OAS patterns have no way to "escape" template expressions.
Which means we can't convert it, but we can validate it.
*/
func exactToPat(src string) string {
if strings.ContainsAny(src, `{}?#`) {
panic(fmt.Errorf(
`[rout] pattern %q contains special characters and can't be converted to an OAS pattern`,
src,
))
}
return src
}
// Copied from `github.com/mitranim/gax` and tested there.
func growBytes(prev []byte, size int) []byte {
len, cap := len(prev), cap(prev)
if cap-len >= size {
return prev
}
// Similar to the usual slice growth rules. May allocate more than asked.
next := make([]byte, len, 2*cap+size)
copy(next, prev)
return next
}
/*
Estimates the length of `Pat` that would be generated by parsing the given
pattern. Assumes a well-formed pattern. For invalid patterns, this may result
in some wasted allocations. We assume that all patterns are small, hardcoded,
and valid, and optimize for that case.
*/
func patLen(src string) (out int) {
hit := false
for _, char := range src {
switch char {
case '{':
out++
hit = true
case '}':
hit = false
default:
if !hit {
out++
hit = true
}
}
}
return
}
// Short for "submatches". Used by `Pat` when matching strings.
type subs struct {
buf [subsCap]string
cur int
}
func (self *subs) add(val string) bool {
if len(val) == 0 {
return false
}
if self.cur < subsCap {
self.buf[self.cur] = val
self.cur++
return true
}
return false
}
func (self *subs) slice() []string {
return self.buf[:self.cur]
}
func strPop(ptr *string, cur int) (out string) {
out, *ptr = (*ptr)[:cur], (*ptr)[cur:]
return
}
func matchExa(pat, inp string) bool { return pat == inp }
func matchSta(pat, inp string) bool {
return strings.HasPrefix(inp, pat) &&
(len(inp) == len(pat) ||
hasSlashSuffix(pat) ||
hasSlashPrefix(inp[len(pat):]))
}
func matchReg(pat, inp string) bool {
return cachedRegexp(pat).MatchString(inp)
}
func matchPat(pat, inp string) bool {
return cachedPat(pat).Match(inp)
}
func submatchExa(pat, inp string) []string {
if matchExa(pat, inp) {
return []string{}
}
return nil
}
func submatchSta(pat, inp string) []string {
if matchSta(pat, inp) {
return []string{}
}
return nil
}
func submatchReg(pat, inp string) []string {
match := cachedRegexp(pat).FindStringSubmatch(inp)
if len(match) >= 1 {
return match[1:]
}
return nil
}
func submatchPat(pat, inp string) []string {
return cachedPat(pat).Submatch(inp)
}