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