/
namespace.go
197 lines (181 loc) · 5.82 KB
/
namespace.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
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
package checkers
import (
"sort"
"strings"
"unicode"
"unicode/utf8"
errgo "gopkg.in/errgo.v1"
)
// Namespace holds maps from schema URIs to the
// prefixes that are used to encode them in first party
// caveats. Several different URIs may map to the same
// prefix - this is usual when several different backwardly
// compatible schema versions are registered.
type Namespace struct {
uriToPrefix map[string]string
}
// NewNamespace returns a new namespace with the
// given initial contents. It will panic if any of the
// URI keys or their associated prefix are invalid
// (see IsValidSchemaURI and IsValidPrefix).
func NewNamespace(uriToPrefix map[string]string) *Namespace {
ns := &Namespace{
uriToPrefix: make(map[string]string),
}
for uri, prefix := range uriToPrefix {
ns.Register(uri, prefix)
}
return ns
}
// String returns the namespace representation as returned by
// ns.MarshalText.
func (ns *Namespace) String() string {
data, _ := ns.MarshalText()
return string(data)
}
// MarshalText implements encoding.TextMarshaler by
// returning all the elements in the namespace sorted by
// URI, joined to the associated prefix with a colon and
// separated with spaces.
func (ns *Namespace) MarshalText() ([]byte, error) {
if ns == nil || len(ns.uriToPrefix) == 0 {
return nil, nil
}
uris := make([]string, 0, len(ns.uriToPrefix))
dataLen := 0
for uri, prefix := range ns.uriToPrefix {
uris = append(uris, uri)
dataLen += len(uri) + 1 + len(prefix) + 1
}
sort.Strings(uris)
data := make([]byte, 0, dataLen)
for i, uri := range uris {
if i > 0 {
data = append(data, ' ')
}
data = append(data, uri...)
data = append(data, ':')
data = append(data, ns.uriToPrefix[uri]...)
}
return data, nil
}
func (ns *Namespace) UnmarshalText(data []byte) error {
uriToPrefix := make(map[string]string)
elems := strings.Fields(string(data))
for _, elem := range elems {
i := strings.LastIndex(elem, ":")
if i == -1 {
return errgo.Newf("no colon in namespace field %q", elem)
}
uri, prefix := elem[0:i], elem[i+1:]
if !IsValidSchemaURI(uri) {
// Currently this can't happen because the only invalid URIs
// are those which contain a space
return errgo.Newf("invalid URI %q in namespace field %q", uri, elem)
}
if !IsValidPrefix(prefix) {
return errgo.Newf("invalid prefix %q in namespace field %q", prefix, elem)
}
if _, ok := uriToPrefix[uri]; ok {
return errgo.Newf("duplicate URI %q in namespace %q", uri, data)
}
uriToPrefix[uri] = prefix
}
ns.uriToPrefix = uriToPrefix
return nil
}
// EnsureResolved tries to resolve the given schema URI to a prefix and
// returns the prefix and whether the resolution was successful. If the
// URI hasn't been registered but a compatible version has, the
// given URI is registered with the same prefix.
func (ns *Namespace) EnsureResolved(uri string) (string, bool) {
// TODO(rog) compatibility
return ns.Resolve(uri)
}
// Resolve resolves the given schema URI to its registered prefix and
// returns the prefix and whether the resolution was successful.
//
// If ns is nil, it is treated as if it were empty.
//
// Resolve does not mutate ns and may be called concurrently
// with other non-mutating Namespace methods.
func (ns *Namespace) Resolve(uri string) (string, bool) {
if ns == nil {
return "", false
}
prefix, ok := ns.uriToPrefix[uri]
return prefix, ok
}
// ResolveCaveat resolves the given caveat by using
// Resolve to map from its schema namespace to the appropriate prefix using
// Resolve. If there is no registered prefix for the namespace,
// it returns an error caveat.
//
// If ns.Namespace is empty or ns.Location is non-empty, it returns cav unchanged.
//
// If ns is nil, it is treated as if it were empty.
//
// ResolveCaveat does not mutate ns and may be called concurrently
// with other non-mutating Namespace methods.
func (ns *Namespace) ResolveCaveat(cav Caveat) Caveat {
// TODO(rog) If a namespace isn't registered, try to resolve it by
// resolving it to the latest compatible version that is
// registered.
if cav.Namespace == "" || cav.Location != "" {
return cav
}
prefix, ok := ns.Resolve(cav.Namespace)
if !ok {
errCav := ErrorCaveatf("caveat %q in unregistered namespace %q", cav.Condition, cav.Namespace)
if errCav.Namespace != cav.Namespace {
prefix, _ = ns.Resolve(errCav.Namespace)
}
cav = errCav
}
if prefix != "" {
cav.Condition = ConditionWithPrefix(prefix, cav.Condition)
}
cav.Namespace = ""
return cav
}
// ConditionWithPrefix returns the given string prefixed by the
// given prefix. If the prefix is non-empty, a colon
// is used to separate them.
func ConditionWithPrefix(prefix, condition string) string {
if prefix == "" {
return condition
}
return prefix + ":" + condition
}
// Register registers the given URI and associates it
// with the given prefix. If the URI has already been registered,
// this is a no-op.
func (ns *Namespace) Register(uri, prefix string) {
if !IsValidSchemaURI(uri) {
panic(errgo.Newf("cannot register invalid URI %q (prefix %q)", uri, prefix))
}
if !IsValidPrefix(prefix) {
panic(errgo.Newf("cannot register invalid prefix %q for URI %q", prefix, uri))
}
if _, ok := ns.uriToPrefix[uri]; !ok {
ns.uriToPrefix[uri] = prefix
}
}
func invalidSchemaRune(r rune) bool {
return unicode.IsSpace(r)
}
// IsValidSchemaURI reports whether the given argument is suitable for
// use as a namespace schema URI. It must be non-empty, a valid UTF-8
// string and it must not contain white space.
func IsValidSchemaURI(uri string) bool {
// TODO more stringent requirements?
return len(uri) > 0 &&
utf8.ValidString(uri) &&
strings.IndexFunc(uri, invalidSchemaRune) == -1
}
func invalidPrefixRune(r rune) bool {
return r == ' ' || r == ':' || unicode.IsSpace(r)
}
func IsValidPrefix(prefix string) bool {
return utf8.ValidString(prefix) && strings.IndexFunc(prefix, invalidPrefixRune) == -1
}