-
Notifications
You must be signed in to change notification settings - Fork 191
/
path_keys.go
103 lines (96 loc) · 3.79 KB
/
path_keys.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
package types
import (
"bytes"
"fmt"
"regexp"
"strings"
)
// - A "path" is a sequence of zero or more dot-separated nonempty segments
// using a restricted alphabet of ASCII alphanumerics plus underscore and dash,
// consistent with packages/internal/src/lib-chainStorage.js but not currently
// enforcing a length restriction on path segments.
// So `""`, `"foo"`, and `"foo.bar__baz.qux--quux"` are paths but `"."`,
// `"foo/bar"`, `"fo\to"`, and `"foö"` are not.
// This alphabet might be expanded in the future, but such expansion SHOULD NOT
// include control characters (including those that are not ASCII, such as
// U+202E RIGHT-TO-LEFT OVERRIDE), slash `/` (which separates ABCI request path
// segments in e.g. `custom/vstorage/data/foo`), or backslash `\` (which should
// be reserved for adding escape sequences).
//
// - An encoded key for a path is the path prefixed with its length (in ASCII
// digits), separated by nul, followed by the path with dots replaced with nul.
// So the path key for the empty path is `0\0`.
//
// - Store entries exist if and only if self or some descendant has an entry
// with data.
//
// - Store entries with data contain `\0`-prefixed data, (just `\0` if data is
// empty).
//
// - Placeholder store entries contain a single `\255` byte. These are used to
// indicate that the entry does not have any data (which is different from
// empty data). Placeholder entries are used when a descendant with data exists,
// similar to empty non-terminals in the DNS
// (cf. https://www.rfc-editor.org/rfc/rfc8499.html#section-7 ).
var (
EncodedKeySeparator = []byte{0}
PathSeparator = "."
EncodedDataPrefix = []byte{0}
EncodedNoDataValue = []byte{255}
)
// EncodedKeyToPath converts a byte slice key to a string path
func EncodedKeyToPath(key []byte) string {
// Split the key into its path depth and path components.
split := bytes.SplitN(key, EncodedKeySeparator, 2)
encodedPath := split[1]
pathBytes := bytes.ReplaceAll(encodedPath, EncodedKeySeparator, []byte(PathSeparator))
return string(pathBytes)
}
var pathSegmentPattern = `[a-zA-Z0-9_-]+`
var pathSeparatorPattern = `\` + PathSeparator
var pathPattern = fmt.Sprintf(`^$|^%[1]s(%[2]s%[1]s)*$`, pathSegmentPattern, pathSeparatorPattern)
var pathMatcher = regexp.MustCompile(pathPattern)
func ValidatePath(path string) error {
if pathMatcher.MatchString(path) {
return nil
}
// Rescan the string to give a useful error message.
if strings.HasPrefix(path, PathSeparator) {
return fmt.Errorf("path %q starts with separator", path)
}
if strings.HasSuffix(path, PathSeparator) {
return fmt.Errorf("path %q ends with separator", path)
}
if strings.Contains(path, PathSeparator+PathSeparator) {
return fmt.Errorf("path %q contains doubled separators", path)
}
return fmt.Errorf("path %q contains invalid characters", path)
}
// PathToEncodedKey converts a path to a byte slice key
func PathToEncodedKey(path string) []byte {
if err := ValidatePath(path); err != nil {
panic(err)
}
depth := strings.Count(path, PathSeparator)
encodedPath := PathSeparator + path
if len(path) > 0 {
// Increment so that only the empty path is at depth 0.
depth += 1
}
encoded := []byte(fmt.Sprintf("%d%s", depth, encodedPath))
return bytes.ReplaceAll(encoded, []byte(PathSeparator), EncodedKeySeparator)
}
// PathToChildrenPrefix converts a path to a prefix for its children
func PathToChildrenPrefix(path string) []byte {
if err := ValidatePath(path); err != nil {
panic(err)
}
encodedPrefix := PathSeparator + path
if len(path) > 0 {
// Append so that only the empty prefix has no trailing separator.
encodedPrefix += PathSeparator
}
depth := strings.Count(encodedPrefix, PathSeparator)
encoded := []byte(fmt.Sprintf("%d%s", depth, encodedPrefix))
return bytes.ReplaceAll(encoded, []byte(PathSeparator), EncodedKeySeparator)
}