Chromium Code Reviews| 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 |