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 // adapted from github.com/golang/appengine/datastore | |
6 | |
7 package helper | |
8 | |
9 import ( | |
10 "bytes" | |
11 "encoding/base64" | |
12 "errors" | |
13 "strconv" | |
14 "strings" | |
15 | |
16 "github.com/luci/gae" | |
17 pb "github.com/luci/gae/helper/internal/protos/datastore" | |
18 | |
19 "github.com/golang/protobuf/proto" | |
20 ) | |
21 | |
22 // DSKeyEncode encodes the provided key as a base64-encoded protobuf. | |
23 // | |
24 // This encoding is compatible with the SDK-provided encoding and is agnostic | |
25 // to the underlying implementation of the DSKey. | |
26 func DSKeyEncode(k gae.DSKey) string { | |
27 n := 0 | |
28 for i := k; i != nil; i = i.Parent() { | |
29 n++ | |
30 } | |
31 e := make([]*pb.Path_Element, n) | |
32 for i := k; i != nil; i = i.Parent() { | |
33 n-- | |
34 kind := i.Kind() | |
35 e[n] = &pb.Path_Element{ | |
36 Type: &kind, | |
37 } | |
38 // At most one of {Name,Id} should be set. | |
39 // Neither will be set for incomplete keys. | |
40 if i.StringID() != "" { | |
41 sid := i.StringID() | |
42 e[n].Name = &sid | |
43 } else if i.IntID() != 0 { | |
44 iid := i.IntID() | |
45 e[n].Id = &iid | |
46 } | |
47 } | |
48 var namespace *string | |
49 if k.Namespace() != "" { | |
50 namespace = proto.String(k.Namespace()) | |
51 } | |
52 r, err := proto.Marshal(&pb.Reference{ | |
53 App: proto.String(k.AppID()), | |
54 NameSpace: namespace, | |
55 Path: &pb.Path{ | |
56 Element: e, | |
57 }, | |
58 }) | |
59 if err != nil { | |
60 panic(err) | |
61 } | |
62 | |
63 // trim padding | |
64 return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=") | |
65 } | |
66 | |
67 // DSKeyToksDecode decodes a base64-encoded protobuf representation of a DSKey | |
68 // into a tokenized form. This is so that implementations of the gae wrapper | |
69 // can decode to their own implementation of DSKey. | |
70 // | |
71 // This encoding is compatible with the SDK-provided encoding and is agnostic | |
72 // to the underlying implementation of the DSKey. | |
73 func DSKeyToksDecode(encoded string) (appID, namespace string, toks []gae.DSKeyT
ok, err error) { | |
74 // Re-add padding | |
75 if m := len(encoded) % 4; m != 0 { | |
76 encoded += strings.Repeat("=", 4-m) | |
77 } | |
78 b, err := base64.URLEncoding.DecodeString(encoded) | |
79 if err != nil { | |
80 return | |
81 } | |
82 | |
83 r := &pb.Reference{} | |
84 if err = proto.Unmarshal(b, r); err != nil { | |
85 return | |
86 } | |
87 | |
88 appID = r.GetApp() | |
89 namespace = r.GetNameSpace() | |
90 toks = make([]gae.DSKeyTok, len(r.Path.Element)) | |
91 for i, e := range r.Path.Element { | |
92 toks[i] = gae.DSKeyTok{ | |
93 Kind: e.GetType(), | |
94 IntID: e.GetId(), | |
95 StringID: e.GetName(), | |
96 } | |
97 } | |
98 return | |
99 } | |
100 | |
101 // DSKeyMarshalJSON returns a MarshalJSON-compatible serialization of a DSKey. | |
102 func DSKeyMarshalJSON(k gae.DSKey) ([]byte, error) { | |
103 return []byte(`"` + DSKeyEncode(k) + `"`), nil | |
104 } | |
105 | |
106 // DSKeyUnmarshalJSON returns the tokenized version of a DSKey as encoded by | |
107 // DSKeyMarshalJSON. | |
108 func DSKeyUnmarshalJSON(buf []byte) (appID, namespace string, toks []gae.DSKeyTo
k, err error) { | |
109 if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { | |
110 err = errors.New("datastore: bad JSON key") | |
111 } else { | |
112 appID, namespace, toks, err = DSKeyToksDecode(string(buf[1 : len
(buf)-1])) | |
113 } | |
114 return | |
115 } | |
116 | |
117 // DSKeyIncomplete returns true iff k doesn't have an id yet. | |
118 func DSKeyIncomplete(k gae.DSKey) bool { | |
119 return k != nil && k.StringID() == "" && k.IntID() == 0 | |
120 } | |
121 | |
122 // DSKeyValid determines if a key is valid, according to a couple rules: | |
123 // - k is not nil | |
124 // - k's namespace matches ns | |
125 // - every token of k: | |
126 // - (if !allowSpecial) token's kind doesn't start with '__' | |
127 // - token's kind and appid are non-blank | |
128 // - token is not incomplete | |
129 // - all tokens have the same namespace and appid | |
130 func DSKeyValid(k gae.DSKey, ns string, allowSpecial bool) bool { | |
131 if k == nil { | |
132 return false | |
133 } | |
134 // since we do "client-side" validation of namespaces in local | |
135 // implementations, it's convenient to check this here. | |
136 if k.Namespace() != ns { | |
137 return false | |
138 } | |
139 for ; k != nil; k = k.Parent() { | |
140 if !allowSpecial && len(k.Kind()) >= 2 && k.Kind()[:2] == "__" { | |
141 return false | |
142 } | |
143 if k.Kind() == "" || k.AppID() == "" { | |
144 return false | |
145 } | |
146 if k.StringID() != "" && k.IntID() != 0 { | |
147 return false | |
148 } | |
149 if k.Parent() != nil { | |
150 if DSKeyIncomplete(k.Parent()) { | |
151 return false | |
152 } | |
153 if k.Parent().AppID() != k.AppID() || k.Parent().Namespa
ce() != k.Namespace() { | |
154 return false | |
155 } | |
156 } | |
157 } | |
158 return true | |
159 } | |
160 | |
161 // DSKeyRoot returns the entity root for the given key. | |
162 func DSKeyRoot(k gae.DSKey) gae.DSKey { | |
163 for k != nil && k.Parent() != nil { | |
164 k = k.Parent() | |
165 } | |
166 return k | |
167 } | |
168 | |
169 // DSKeysEqual returns true iff the two keys represent identical key values. | |
170 func DSKeysEqual(a, b gae.DSKey) (ret bool) { | |
171 ret = (a.Kind() == b.Kind() && | |
172 a.StringID() == b.StringID() && | |
173 a.IntID() == b.IntID() && | |
174 a.AppID() == b.AppID() && | |
175 a.Namespace() == b.Namespace()) | |
176 if !ret { | |
177 return | |
178 } | |
179 ap, bp := a.Parent(), b.Parent() | |
180 return (ap == nil && bp == nil) || DSKeysEqual(ap, bp) | |
181 } | |
182 | |
183 func marshalDSKey(b *bytes.Buffer, k gae.DSKey) { | |
184 if k.Parent() != nil { | |
185 marshalDSKey(b, k.Parent()) | |
186 } | |
187 b.WriteByte('/') | |
188 b.WriteString(k.Kind()) | |
189 b.WriteByte(',') | |
190 if k.StringID() != "" { | |
191 b.WriteString(k.StringID()) | |
192 } else { | |
193 b.WriteString(strconv.FormatInt(k.IntID(), 10)) | |
194 } | |
195 } | |
196 | |
197 // DSKeyString returns a human-readable representation of the key, and is the | |
198 // typical implementation of DSKey.String() (though it isn't guaranteed to be) | |
199 func DSKeyString(k gae.DSKey) string { | |
200 if k == nil { | |
201 return "" | |
202 } | |
203 b := bytes.NewBuffer(make([]byte, 0, 512)) | |
204 marshalDSKey(b, k) | |
205 return b.String() | |
206 } | |
207 | |
208 // DSKeySplit splits the key into its constituent parts. Note that if the key is | |
209 // not DSKeyValid, this method may not provide a round-trip for k. | |
210 func DSKeySplit(k gae.DSKey) (appID, namespace string, toks []gae.DSKeyTok) { | |
211 if k == nil { | |
212 return | |
213 } | |
214 | |
215 if sk, ok := k.(*GenericDSKey); ok { | |
216 if sk == nil { | |
217 return | |
218 } | |
219 return sk.appID, sk.namespace, sk.toks | |
220 } | |
221 | |
222 n := 0 | |
223 for i := k; i != nil; i = i.Parent() { | |
224 n++ | |
225 } | |
226 toks = make([]gae.DSKeyTok, n) | |
227 for i := k; i != nil; i = i.Parent() { | |
228 n-- | |
229 toks[n].IntID = i.IntID() | |
230 toks[n].StringID = i.StringID() | |
231 toks[n].Kind = i.Kind() | |
232 } | |
233 appID = k.AppID() | |
234 namespace = k.Namespace() | |
235 return | |
236 } | |
OLD | NEW |