-
-
Notifications
You must be signed in to change notification settings - Fork 69
/
parameterizable.go
134 lines (121 loc) Β· 3.5 KB
/
parameterizable.go
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
package goyave
import (
"fmt"
"regexp"
"strings"
)
// parameterizable represents a route or router accepting
// parameters in its URI.
type parameterizable struct {
regex *regexp.Regexp
parameters []string
}
// compileParameters parse the route parameters and compiles their regexes if needed.
// If "ends" is set to true, the generated regex ends with "$", thus set "ends" to true
// if you're compiling route parameters, set to false if you're compiling router parameters.
func (p *parameterizable) compileParameters(uri string, ends bool, regexCache map[string]*regexp.Regexp) {
idxs, err := p.braceIndices(uri)
if err != nil {
panic(err)
}
var builder strings.Builder
// Final regex will never be larger than src uri + 2 (for ^ and $)
// Make initial alloc to avoid the need for realloc
builder.Grow(len(uri) + 2)
builder.WriteString("^")
length := len(idxs)
if length > 0 {
end := 0
for i := 0; i < length; i += 2 {
raw := uri[end:idxs[i]]
end = idxs[i+1]
sub := uri[idxs[i]+1 : end]
parts := strings.SplitN(sub, ":", 2)
if parts[0] == "" {
panic(fmt.Errorf("invalid route parameter, missing name in %q", sub))
}
pattern := "[^/]+" // default pattern
if len(parts) == 2 {
pattern = parts[1]
if pattern == "" {
panic(fmt.Errorf("invalid route parameter, missing pattern in %q", sub))
}
}
builder.WriteString(raw)
builder.WriteString("(")
builder.WriteString(pattern)
builder.WriteString(")")
end++ // Skip closing braces
p.parameters = append(p.parameters, parts[0])
}
builder.WriteString(uri[end:])
} else {
builder.WriteString(uri)
}
if ends {
builder.WriteString("$")
} else {
builder.WriteString(`/?$`)
}
pattern := builder.String()
cachedRegex, ok := regexCache[pattern]
if !ok {
regex := regexp.MustCompile(pattern)
regexCache[pattern] = regex
p.regex = regex
} else {
p.regex = cachedRegex
}
if p.regex.NumSubexp() != length/2 {
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", uri) +
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
}
}
// braceIndices returns the first level curly brace indices from a string.
// Returns an error in case of unbalanced braces.
func (p *parameterizable) braceIndices(s string) ([]int, error) {
var level, idx int
indices := make([]int, 0, 2)
length := len(s)
for i := 0; i < length; i++ {
if s[i] == '{' {
level++
if level == 1 {
idx = i
}
} else if s[i] == '}' {
level--
if level == 0 {
if i == idx+1 {
return nil, fmt.Errorf("empty route parameter in %q", s)
}
indices = append(indices, idx, i)
} else if level < 0 {
return nil, fmt.Errorf("unbalanced braces in %q", s)
}
}
}
if level != 0 {
return nil, fmt.Errorf("unbalanced braces in %q", s)
}
return indices, nil
}
// makeParameters from a regex match and the given parameter names.
// The match parameter is expected to contain only the capturing groups.
//
// Given ["/product/33/param", "33", "param"] ["id", "name"]
// The returned map will be ["id": "33", "name": "param"]
func (p *parameterizable) makeParameters(match []string, names []string) map[string]string {
length := len(match)
params := make(map[string]string, length-1)
for i := 1; i < length; i++ {
params[names[i-1]] = match[i]
}
return params
}
// GetParameters returns the URI parameters' names (in order of appearance).
func (p *parameterizable) GetParameters() []string {
cpy := make([]string, len(p.parameters))
copy(cpy, p.parameters)
return cpy
}