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
|
package radix
import (
"fmt"
"regexp"
"strings"
"unicode/utf8"
"github.com/valyala/bytebufferpool"
)
func panicf(s string, args ...interface{}) {
panic(fmt.Sprintf(s, args...))
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func bufferRemoveString(buf *bytebufferpool.ByteBuffer, s string) {
buf.B = buf.B[:len(buf.B)-len(s)]
}
// func isIndexEqual(a, b string) bool {
// ra, _ := utf8.DecodeRuneInString(a)
// rb, _ := utf8.DecodeRuneInString(b)
// return unicode.ToLower(ra) == unicode.ToLower(rb)
// }
// longestCommonPrefix finds the longest common prefix.
// This also implies that the common prefix contains no ':' or '*'
// since the existing key can't contain those chars.
func longestCommonPrefix(a, b string) int {
i := 0
max := min(utf8.RuneCountInString(a), utf8.RuneCountInString(b))
for i < max {
ra, sizeA := utf8.DecodeRuneInString(a)
rb, sizeB := utf8.DecodeRuneInString(b)
a = a[sizeA:]
b = b[sizeB:]
if ra != rb {
return i
}
i += sizeA
}
return i
}
// segmentEndIndex returns the index where the segment ends from the given path
func segmentEndIndex(path string, includeTSR bool) int {
end := 0
for end < len(path) && path[end] != '/' {
end++
}
if includeTSR && path[end:] == "/" {
end++
}
return end
}
// findWildPath search for a wild path segment and check the name for invalid characters.
// Returns -1 as index, if no param/wildcard was found.
func findWildPath(path string, fullPath string) *wildPath {
// Find start
for start, c := range []byte(path) {
// A wildcard starts with ':' (param) or '*' (wildcard)
if c != '{' {
continue
}
withRegex := false
keys := 0
// Find end and check for invalid characters
for end, c := range []byte(path[start+1:]) {
switch c {
case '}':
if keys > 0 {
keys--
continue
}
end := start + end + 2
wp := &wildPath{
path: path[start:end],
keys: []string{path[start+1 : end-1]},
start: start,
end: end,
pType: param,
}
if len(path) > end && path[end] == '{' {
panic("the wildcards must be separated by at least 1 char")
}
sn := strings.SplitN(wp.keys[0], ":", 2)
if len(sn) > 1 {
wp.keys = []string{sn[0]}
pattern := sn[1]
if pattern == "*" {
wp.pattern = pattern
wp.pType = wildcard
} else {
wp.pattern = "(" + pattern + ")"
wp.regex = regexp.MustCompile(wp.pattern)
}
} else if path[len(path)-1] != '/' {
wp.pattern = "(.*)"
}
if len(wp.keys[0]) == 0 {
panicf("wildcards must be named with a non-empty name in path '%s'", fullPath)
}
segEnd := end + segmentEndIndex(path[end:], true)
path = path[end:segEnd]
if path == "/" {
// Last segment, so include the TSR
path = ""
wp.end++
}
if len(path) > 0 {
// Rebuild the wildpath with the prefix
wp2 := findWildPath(path, fullPath)
if wp2 != nil {
prefix := path[:wp2.start]
wp.end += wp2.end
wp.path += prefix + wp2.path
wp.pattern += prefix + wp2.pattern
wp.keys = append(wp.keys, wp2.keys...)
} else {
wp.path += path
wp.pattern += path
wp.end += len(path)
}
wp.regex = regexp.MustCompile(wp.pattern)
}
return wp
case ':':
withRegex = true
case '{':
if !withRegex && keys == 0 {
panic("the char '{' is not allowed in the param name")
}
keys++
}
}
}
return nil
}
|