OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package memory | |
6 | |
7 import ( | |
8 "fmt" | |
9 | |
M-A Ruel
2015/05/25 18:21:10
group stdlib. seriously, I prefer goimports native
iannucci
2015/05/27 19:33:32
done
| |
10 "bytes" | |
11 | |
12 "github.com/luci/luci-go/common/funnybase" | |
13 | |
14 "appengine/datastore" | |
15 "appengine_internal" | |
16 basepb "appengine_internal/base" | |
17 "github.com/golang/protobuf/proto" | |
18 | |
19 "github.com/mjibson/goon" | |
20 ) | |
21 | |
22 ///////////////////////////// fakeGAECtxForNewKey ////////////////////////////// | |
23 | |
24 type fakeGAECtxForNewKey string | |
25 | |
26 func (fakeGAECtxForNewKey) Debugf(format string, args ...interface{}) {} | |
27 func (fakeGAECtxForNewKey) Infof(format string, args ...interface{}) {} | |
28 func (fakeGAECtxForNewKey) Warningf(format string, args ...interface{}) {} | |
29 func (fakeGAECtxForNewKey) Errorf(format string, args ...interface{}) {} | |
30 func (fakeGAECtxForNewKey) Criticalf(format string, args ...interface{}) {} | |
31 func (fakeGAECtxForNewKey) Request() interface{} { retur n nil } | |
32 func (f fakeGAECtxForNewKey) Call(service, method string, in, out appengine_inte rnal.ProtoMessage, opts *appengine_internal.CallOptions) error { | |
33 if service != "__go__" || method != "GetNamespace" { | |
34 panic(fmt.Errorf("fakeGAECtxForNewKey: cannot facilitate Call(%q , %q, ...)", service, method)) | |
35 } | |
36 out.(*basepb.StringProto).Value = proto.String(string(f)) | |
37 return nil | |
38 } | |
39 func (fakeGAECtxForNewKey) FullyQualifiedAppID() string { return "dev~my~app" } | |
40 | |
41 /////////////////////////////// Key construction /////////////////////////////// | |
42 | |
43 func newKey(ns, kind, stringID string, intID int64, parent *datastore.Key) *data store.Key { | |
44 return datastore.NewKey(fakeGAECtxForNewKey(ns), kind, stringID, intID, parent) | |
45 } | |
46 func newKeyObjError(ns string, knr goon.KindNameResolver, src interface{}) (*dat astore.Key, error) { | |
47 return (&goon.Goon{ | |
48 Context: fakeGAECtxForNewKey(ns), | |
49 KindNameResolver: knr}).KeyError(src) | |
50 } | |
51 func newKeyObj(ns string, knr goon.KindNameResolver, obj interface{}) *datastore .Key { | |
52 k, err := newKeyObjError(ns, knr, obj) | |
53 if err != nil { | |
54 panic(err) | |
55 } | |
56 return k | |
57 } | |
58 func kind(ns string, knr goon.KindNameResolver, src interface{}) string { | |
59 return newKeyObj(ns, knr, src).Kind() | |
60 } | |
61 | |
62 /////////////////////////////// Binary Encoding //////////////////////////////// | |
63 | |
64 type keyTok struct { | |
65 kind string | |
66 intID uint64 | |
67 stringID string | |
68 } | |
69 | |
70 func keyToToks(key *datastore.Key) (namespace string, ret []*keyTok) { | |
71 var inner func(*datastore.Key) | |
72 inner = func(k *datastore.Key) { | |
73 if k.Parent() != nil { | |
74 inner(k.Parent()) | |
75 } | |
76 ret = append(ret, &keyTok{ | |
M-A Ruel
2015/05/25 18:21:10
put on one line
iannucci
2015/05/27 19:33:32
find/replace fail. done
| |
77 k.Kind(), uint64(k.IntID()), k.StringID()}) | |
78 } | |
79 inner(key) | |
80 namespace = key.Namespace() | |
81 return | |
82 } | |
83 | |
84 func toksToKey(ns string, toks []*keyTok) *datastore.Key { | |
M-A Ruel
2015/05/25 18:21:10
Since you use a named return value in the other me
iannucci
2015/05/27 19:33:32
plus 4 characters! :P Done
| |
85 ret := (*datastore.Key)(nil) | |
86 for _, t := range toks { | |
87 ret = newKey(ns, t.kind, t.stringID, int64(t.intID), ret) | |
88 } | |
89 return ret | |
90 } | |
91 | |
92 type nsOption bool | |
93 | |
94 const ( | |
95 withNS nsOption = iota == 0 | |
M-A Ruel
2015/05/25 18:21:10
Too clever for no value, please use true and false
iannucci
2015/05/27 19:33:32
(technically, it's for TWO values.... :D)
done
| |
96 noNS | |
97 ) | |
98 | |
99 func keyBytes(nso nsOption, k *datastore.Key) []byte { | |
100 buf := &bytes.Buffer{} | |
101 writeKey(buf, nso, k) | |
102 return buf.Bytes() | |
103 } | |
104 | |
105 func keyFromBytes(nso nsOption, d []byte) (*datastore.Key, error) { | |
106 return readKey(bytes.NewBuffer(d), nso) | |
107 } | |
108 func keyFromByteString(nso nsOption, d string) (*datastore.Key, error) { | |
M-A Ruel
2015/05/25 18:21:09
I'd prefer you to only keep one of the 2 functions
iannucci
2015/05/27 19:33:32
turns out I only used one.
| |
109 return readKey(bytes.NewBufferString(d), nso) | |
110 } | |
111 | |
112 func writeKey(buf *bytes.Buffer, nso nsOption, k *datastore.Key) { | |
113 // namespace ++ #tokens ++ [tok.kind ++ tok.stringID ++ tok.intID?]* | |
114 namespace, toks := keyToToks(k) | |
115 if nso == withNS { | |
116 writeString(buf, namespace) | |
117 } | |
118 funnybase.WriteUint(buf, uint64(len(toks))) | |
119 for _, tok := range toks { | |
120 writeString(buf, tok.kind) | |
121 writeString(buf, tok.stringID) | |
122 if tok.stringID == "" { | |
123 funnybase.WriteUint(buf, tok.intID) | |
124 } | |
125 } | |
126 } | |
127 | |
128 func readKey(buf *bytes.Buffer, nso nsOption) (*datastore.Key, error) { | |
129 var err error | |
M-A Ruel
2015/05/25 18:21:10
Not needed
iannucci
2015/05/27 19:33:32
moved into the if block to make its use more clear
| |
130 toks := []*keyTok{} | |
M-A Ruel
2015/05/25 18:21:09
Move to line 143. Make it
toks := make([]*keyTok,
| |
131 namespace := "" | |
132 if nso == withNS { | |
133 namespace, err = readString(buf) | |
134 if err != nil { | |
135 return nil, err | |
136 } | |
137 } | |
138 | |
139 numToks, err := funnybase.ReadUint(buf) | |
M-A Ruel
2015/05/25 18:21:09
You should ensure numToks has a reasonable limit
iannucci
2015/05/27 19:33:32
done
| |
140 if err != nil { | |
141 return nil, err | |
142 } | |
143 | |
144 for i := uint64(0); i < numToks; i++ { | |
145 tok := &keyTok{} | |
146 tok.kind, err = readString(buf) | |
147 if err != nil { | |
148 return nil, err | |
149 } | |
150 tok.stringID, err = readString(buf) | |
151 if err != nil { | |
152 return nil, err | |
153 } | |
154 if tok.stringID == "" { | |
155 tok.intID, err = funnybase.ReadUint(buf) | |
M-A Ruel
2015/05/25 18:21:10
You should ensure intID is positive, non zero.
iannucci
2015/05/27 19:33:32
done
| |
156 if err != nil { | |
157 return nil, err | |
158 } | |
159 } | |
160 toks = append(toks, tok) | |
161 } | |
162 | |
163 return toksToKey(namespace, toks), nil | |
164 } | |
165 | |
166 //////////////////////////////// Key utilities ///////////////////////////////// | |
167 | |
168 func rootKey(key *datastore.Key) *datastore.Key { | |
169 for key.Parent() != nil { | |
170 key = key.Parent() | |
171 } | |
172 return key | |
173 } | |
174 | |
175 type keyValidOption bool | |
176 | |
177 const ( | |
178 // UserKeyOnly is used with KeyValid, and ensures that the key is only o ne | |
179 // that's valid for a user program to write to. | |
180 UserKeyOnly keyValidOption = iota == 0 | |
M-A Ruel
2015/05/25 18:21:10
too clever again
iannucci
2015/05/27 19:33:32
done
| |
181 | |
182 // AllowSpecialKeys is used with KeyValid, and allows keys for special | |
183 // metadata objects (like "__entity_group__"). | |
184 AllowSpecialKeys | |
185 ) | |
186 | |
187 // KeyValid checks to see if a key is valid by applying a bunch of constraint | |
188 // rules to it (e.g. can't have StringID and IntID set at the same time, can't | |
189 // have a parent key which is Incomplete(), etc.) | |
190 // | |
191 // It verifies that the key is also in the provided namespace. It can also | |
192 // reject keys which are 'special' e.g. have a Kind starting with "__". This | |
193 // behavior is controllable with opt. | |
194 func KeyValid(ns string, k *datastore.Key, opt keyValidOption) bool { | |
195 // copied from the appengine SDK because why would any user program need to | |
196 // see if a key is valid? | |
197 if k == nil { | |
198 return false | |
199 } | |
200 // since we do "client-side" validataion of namespaces, check this here. | |
201 if k.Namespace() != ns { | |
202 return false | |
203 } | |
204 for ; k != nil; k = k.Parent() { | |
205 if opt == UserKeyOnly && len(k.Kind()) >= 2 && k.Kind()[:2] == " __" { // reserve all Kinds starting with __ | |
206 return false | |
207 } | |
208 if k.Kind() == "" || k.AppID() == "" { | |
209 return false | |
210 } | |
211 if k.StringID() != "" && k.IntID() != 0 { | |
212 return false | |
213 } | |
214 if k.Parent() != nil { | |
215 if k.Parent().Incomplete() { | |
216 return false | |
217 } | |
218 if k.Parent().AppID() != k.AppID() || k.Parent().Namespa ce() != k.Namespace() { | |
219 return false | |
220 } | |
221 } | |
222 } | |
223 return true | |
224 } | |
225 | |
226 // KeyCouldBeValid is like KeyValid, but it allows for the possibility that the | |
227 // last token of the key is Incomplete(). It returns true if the Key will become | |
228 // valid once it recieves an automatically-assigned ID. | |
229 func KeyCouldBeValid(ns string, k *datastore.Key, opt keyValidOption) bool { | |
230 // adds an id to k if it's incomplete. | |
231 testKey := k | |
232 if k.Incomplete() { | |
233 testKey = newKey(ns, k.Kind(), "", 1, k.Parent()) | |
M-A Ruel
2015/05/25 18:21:10
k = newKey(ns, k.Kind(), "", 1, k.Parent())
you d
iannucci
2015/05/27 19:33:32
yes I do. k could be `newKey(ns, k.Kind(), "foo",
| |
234 } | |
235 return KeyValid(ns, testKey, opt) | |
236 } | |
OLD | NEW |