1use super::wire::{Field, Malformed, Reader, Writer};
21use hidane_proto::google::{
22 datastore::v1::{
23 ArrayValue, Entity, Key, PartitionId, Value,
24 key::{PathElement, path_element::IdType},
25 value::ValueType,
26 },
27 r#type::LatLng,
28};
29use hidane_proto::ordered::Properties;
30use prost_types::Timestamp;
31
32const KEY: u32 = 13;
34const PROPERTY: u32 = 14;
35const RAW_PROPERTY: u32 = 15;
36const ENTITY_GROUP: u32 = 16;
37const APP: u32 = 13;
39const PATH: u32 = 14;
40const NAME_SPACE: u32 = 20;
41const DATABASE_ID: u32 = 23;
42const ELEMENT: u32 = 1;
44const ELEMENT_TYPE: u32 = 2;
45const ELEMENT_ID: u32 = 3;
46const ELEMENT_NAME: u32 = 4;
47const MEANING: u32 = 1;
49const MEANING_URI: u32 = 2;
50const NAME: u32 = 3;
51const MULTIPLE: u32 = 4;
52const VALUE: u32 = 5;
53const INT64: u32 = 1;
55const BOOLEAN: u32 = 2;
56const STRING: u32 = 3;
57const DOUBLE: u32 = 4;
58const POINT: u32 = 5;
59const POINT_X: u32 = 6;
60const POINT_Y: u32 = 7;
61const REFERENCE: u32 = 12;
62const REFERENCE_APP: u32 = 13;
63const REFERENCE_ELEMENT: u32 = 14;
64const REFERENCE_TYPE: u32 = 15;
65const REFERENCE_ID: u32 = 16;
66const REFERENCE_NAME: u32 = 17;
67const REFERENCE_NAME_SPACE: u32 = 20;
68const REFERENCE_DATABASE_ID: u32 = 23;
69const GD_WHEN: i32 = 7;
71const GEORSS_POINT: i32 = 9;
72const BLOB: i32 = 14;
73const BYTESTRING: i32 = 16;
74const ENTITY_PROTO: i32 = 19;
75const ZLIB: i32 = 22;
76const EMPTY_LIST: i32 = 24;
77const ZLIB_URI: &[u8] = b"ZLIB";
78
79pub fn encode(key: &Key, properties: &Properties) -> Vec<u8> {
81 let mut w = Writer::default();
82 entity(&mut w, Some(key), properties);
83 w.into_bytes()
84}
85
86fn entity(w: &mut Writer, key: Option<&Key>, properties: &Properties) {
87 w.message(KEY, |k| match key {
88 Some(key) => {
89 let partition = key.partition_id.clone().unwrap_or_default();
90 k.bytes(APP, partition.project_id.as_bytes());
91 k.message(PATH, |p| {
92 for element in &key.path {
93 p.group(ELEMENT, |e| {
94 path_element(e, element, ELEMENT_TYPE, ELEMENT_ID, ELEMENT_NAME);
95 });
96 }
97 });
98 if !partition.namespace_id.is_empty() {
99 k.bytes(NAME_SPACE, partition.namespace_id.as_bytes());
100 }
101 if !partition.database_id.is_empty() {
102 k.bytes(DATABASE_ID, partition.database_id.as_bytes());
103 }
104 }
105 None => {
106 k.bytes(APP, b"");
107 k.message(PATH, |_| {});
108 }
109 });
110 for indexed in [true, false] {
111 for (name, value) in properties {
112 match &value.value_type {
113 Some(ValueType::ArrayValue(array)) if array.values.is_empty() => {
114 if indexed {
115 w.message(PROPERTY, |p| {
116 p.uint(MEANING, EMPTY_LIST as u64);
117 p.bytes(NAME, name.as_bytes());
118 p.uint(MULTIPLE, 0);
119 p.message(VALUE, |_| {});
120 });
121 }
122 }
123 Some(ValueType::ArrayValue(array)) => {
124 for element in &array.values {
125 if !element.exclude_from_indexes == indexed {
126 property(w, name, element, true);
127 }
128 }
129 }
130 _ => {
131 if !value.exclude_from_indexes == indexed {
132 property(w, name, value, false);
133 }
134 }
135 }
136 }
137 }
138 w.message(ENTITY_GROUP, |g| {
139 if let Some(root) = key.and_then(|key| key.path.first()) {
140 g.group(ELEMENT, |e| {
141 path_element(e, root, ELEMENT_TYPE, ELEMENT_ID, ELEMENT_NAME);
142 });
143 }
144 });
145}
146
147fn path_element(w: &mut Writer, element: &PathElement, kind: u32, id: u32, name: u32) {
148 w.bytes(kind, element.kind.as_bytes());
149 match &element.id_type {
150 Some(IdType::Id(n)) => w.int(id, *n),
151 Some(IdType::Name(text)) => w.bytes(name, text.as_bytes()),
152 None => {}
153 }
154}
155
156fn meaning(value: &Value) -> (Option<i32>, Option<&'static [u8]>) {
158 if value.meaning == ZLIB {
159 return (Some(BLOB), Some(ZLIB_URI));
160 }
161 if value.meaning != 0 {
162 return (Some(value.meaning), None);
163 }
164 let meaning = match &value.value_type {
165 Some(ValueType::TimestampValue(_)) => Some(GD_WHEN),
166 Some(ValueType::GeoPointValue(_)) => Some(GEORSS_POINT),
167 Some(ValueType::EntityValue(_)) => Some(ENTITY_PROTO),
168 Some(ValueType::BlobValue(_)) if value.exclude_from_indexes => Some(BLOB),
169 Some(ValueType::BlobValue(_)) => Some(BYTESTRING),
170 _ => None,
171 };
172 (meaning, None)
173}
174
175fn property(w: &mut Writer, name: &str, value: &Value, multiple: bool) {
176 let field = if value.exclude_from_indexes {
177 RAW_PROPERTY
178 } else {
179 PROPERTY
180 };
181 let (meaning, uri) = meaning(value);
182 w.message(field, |p| {
183 if let Some(meaning) = meaning {
184 #[allow(clippy::cast_sign_loss)]
185 p.uint(MEANING, meaning as u64);
186 }
187 if let Some(uri) = uri {
188 p.bytes(MEANING_URI, uri);
189 }
190 p.bytes(NAME, name.as_bytes());
191 p.uint(MULTIPLE, u64::from(multiple));
192 p.message(VALUE, |v| property_value(v, value));
193 });
194}
195
196fn property_value(w: &mut Writer, value: &Value) {
197 match &value.value_type {
198 Some(ValueType::BooleanValue(b)) => w.uint(BOOLEAN, u64::from(*b)),
199 Some(ValueType::IntegerValue(i)) => w.int(INT64, *i),
200 Some(ValueType::DoubleValue(d)) => w.double(DOUBLE, if *d == 0.0 { 0.0 } else { *d }),
201 Some(ValueType::TimestampValue(ts)) => w.int(INT64, micros(ts)),
202 Some(ValueType::StringValue(s)) => w.bytes(STRING, s.as_bytes()),
203 Some(ValueType::BlobValue(b)) => w.bytes(STRING, b),
204 Some(ValueType::GeoPointValue(point)) => w.group(POINT, |p| {
205 p.double(POINT_X, point.latitude);
206 p.double(POINT_Y, point.longitude);
207 }),
208 Some(ValueType::KeyValue(key)) => w.group(REFERENCE, |r| {
209 let partition = key.partition_id.clone().unwrap_or_default();
210 r.bytes(REFERENCE_APP, partition.project_id.as_bytes());
211 for element in &key.path {
212 r.group(REFERENCE_ELEMENT, |e| {
213 path_element(e, element, REFERENCE_TYPE, REFERENCE_ID, REFERENCE_NAME);
214 });
215 }
216 if !partition.namespace_id.is_empty() {
217 r.bytes(REFERENCE_NAME_SPACE, partition.namespace_id.as_bytes());
218 }
219 if !partition.database_id.is_empty() {
220 r.bytes(REFERENCE_DATABASE_ID, partition.database_id.as_bytes());
221 }
222 }),
223 Some(ValueType::EntityValue(nested)) => {
224 let mut inner = Writer::default();
225 entity(&mut inner, nested.key.as_ref(), &nested.properties);
226 w.bytes(STRING, &inner.into_bytes());
227 }
228 _ => {}
230 }
231}
232
233fn micros(ts: &Timestamp) -> i64 {
234 ts.seconds
235 .saturating_mul(1_000_000)
236 .saturating_add(i64::from(ts.nanos / 1000))
237}
238
239pub fn decode(bytes: &[u8]) -> Result<(Key, Properties), Malformed> {
242 let (key, properties) = decode_entity(bytes)?;
243 let key = key.ok_or(Malformed)?;
244 if key.path.is_empty() {
245 return Err(Malformed);
246 }
247 Ok((key, properties))
248}
249
250fn decode_entity(bytes: &[u8]) -> Result<(Option<Key>, Properties), Malformed> {
251 let mut key = None;
252 let mut properties = Properties::new();
253 for field in Reader::new(bytes) {
254 let (number, value) = field?;
255 match number {
256 KEY => key = decode_key(value)?,
257 PROPERTY => decode_property(&mut properties, value, false)?,
258 RAW_PROPERTY => decode_property(&mut properties, value, true)?,
259 _ => {}
260 }
261 }
262 Ok((key, properties))
263}
264
265fn decode_key(field: Field<'_>) -> Result<Option<Key>, Malformed> {
267 let mut partition = PartitionId::default();
268 let mut path = Vec::new();
269 for field in field.fields().ok_or(Malformed)? {
270 let (number, value) = field?;
271 match number {
272 APP => partition.project_id = text(value)?,
273 PATH => {
274 for field in value.fields().ok_or(Malformed)? {
275 let (number, value) = field?;
276 if number == ELEMENT {
277 path.push(decode_element(
278 value,
279 ELEMENT_TYPE,
280 ELEMENT_ID,
281 ELEMENT_NAME,
282 )?);
283 }
284 }
285 }
286 NAME_SPACE => partition.namespace_id = text(value)?,
287 DATABASE_ID => partition.database_id = text(value)?,
288 _ => {}
289 }
290 }
291 if partition.project_id.is_empty() && path.is_empty() {
292 return Ok(None);
293 }
294 Ok(Some(Key {
295 partition_id: Some(partition),
296 path,
297 }))
298}
299
300fn text(field: Field<'_>) -> Result<String, Malformed> {
301 String::from_utf8(field.as_bytes().ok_or(Malformed)?.to_vec()).map_err(|_| Malformed)
302}
303
304fn decode_element(
305 field: Field<'_>,
306 kind: u32,
307 id: u32,
308 name: u32,
309) -> Result<PathElement, Malformed> {
310 let mut element = PathElement::default();
311 for field in field.fields().ok_or(Malformed)? {
312 let (number, value) = field?;
313 if number == kind {
314 element.kind = text(value)?;
315 } else if number == id {
316 #[allow(clippy::cast_possible_wrap)]
317 let n = value.as_varint().ok_or(Malformed)? as i64;
318 element.id_type = Some(IdType::Id(n));
319 } else if number == name {
320 element.id_type = Some(IdType::Name(text(value)?));
321 }
322 }
323 Ok(element)
324}
325
326fn decode_property(
327 properties: &mut Properties,
328 field: Field<'_>,
329 raw: bool,
330) -> Result<(), Malformed> {
331 let (mut meaning, mut uri, mut name, mut multiple, mut bytes) =
332 (0i32, None, None, false, &[][..]);
333 for field in field.fields().ok_or(Malformed)? {
334 let (number, field) = field?;
335 match number {
336 #[allow(clippy::cast_possible_truncation)]
337 MEANING => meaning = field.as_varint().ok_or(Malformed)? as i32,
338 MEANING_URI => uri = Some(field.as_bytes().ok_or(Malformed)?),
339 NAME => name = Some(text(field)?),
340 MULTIPLE => multiple = field.as_varint().ok_or(Malformed)? != 0,
341 VALUE => bytes = field.as_bytes().ok_or(Malformed)?,
342 _ => {}
343 }
344 }
345 let name = name.ok_or(Malformed)?;
346 if meaning == EMPTY_LIST {
347 properties.insert(name, array(Vec::new()));
348 return Ok(());
349 }
350 let mut value = decode_value(bytes, meaning)?;
351 value.exclude_from_indexes = raw;
352 value.meaning = match (meaning, uri) {
353 (BLOB, Some(ZLIB_URI)) => ZLIB,
354 (GD_WHEN | GEORSS_POINT | ENTITY_PROTO | BLOB | BYTESTRING, _) => 0,
355 (meaning, _) => meaning,
356 };
357 if multiple {
358 let entry = properties.entry(name).or_insert_with(|| array(Vec::new()));
359 if let Some(ValueType::ArrayValue(array)) = &mut entry.value_type {
360 array.values.push(value);
361 }
362 } else {
363 properties.insert(name, value);
364 }
365 Ok(())
366}
367
368fn array(values: Vec<Value>) -> Value {
369 Value {
370 value_type: Some(ValueType::ArrayValue(ArrayValue { values })),
371 ..Value::default()
372 }
373}
374
375fn decode_value(bytes: &[u8], meaning: i32) -> Result<Value, Malformed> {
376 let mut value_type = Some(ValueType::NullValue(0));
377 for field in Reader::new(bytes) {
378 let (number, field) = field?;
379 value_type = Some(match number {
380 INT64 => {
381 #[allow(clippy::cast_possible_wrap)]
382 let n = field.as_varint().ok_or(Malformed)? as i64;
383 if meaning == GD_WHEN {
384 ValueType::TimestampValue(Timestamp {
385 seconds: n.div_euclid(1_000_000),
386 #[allow(clippy::cast_possible_truncation)]
387 nanos: (n.rem_euclid(1_000_000) * 1000) as i32,
388 })
389 } else {
390 ValueType::IntegerValue(n)
391 }
392 }
393 BOOLEAN => ValueType::BooleanValue(field.as_varint().ok_or(Malformed)? != 0),
394 DOUBLE => ValueType::DoubleValue(f64::from_bits(field.as_fixed64().ok_or(Malformed)?)),
395 STRING => {
396 let data = field.as_bytes().ok_or(Malformed)?;
397 match meaning {
398 ENTITY_PROTO => {
399 let (key, properties) = decode_entity(data)?;
400 ValueType::EntityValue(Entity { key, properties })
401 }
402 BLOB | BYTESTRING | ZLIB => ValueType::BlobValue(data.to_vec()),
403 _ => ValueType::StringValue(
404 String::from_utf8(data.to_vec()).map_err(|_| Malformed)?,
405 ),
406 }
407 }
408 POINT => {
409 let (mut latitude, mut longitude) = (0.0, 0.0);
410 for field in field.fields().ok_or(Malformed)? {
411 let (number, field) = field?;
412 match number {
413 POINT_X => latitude = f64::from_bits(field.as_fixed64().ok_or(Malformed)?),
414 POINT_Y => longitude = f64::from_bits(field.as_fixed64().ok_or(Malformed)?),
415 _ => {}
416 }
417 }
418 ValueType::GeoPointValue(LatLng {
419 latitude,
420 longitude,
421 })
422 }
423 REFERENCE => {
424 let mut partition = PartitionId::default();
425 let mut path = Vec::new();
426 for field in field.fields().ok_or(Malformed)? {
427 let (number, field) = field?;
428 match number {
429 REFERENCE_APP => partition.project_id = text(field)?,
430 REFERENCE_ELEMENT => path.push(decode_element(
431 field,
432 REFERENCE_TYPE,
433 REFERENCE_ID,
434 REFERENCE_NAME,
435 )?),
436 REFERENCE_NAME_SPACE => partition.namespace_id = text(field)?,
437 REFERENCE_DATABASE_ID => partition.database_id = text(field)?,
438 _ => {}
439 }
440 }
441 ValueType::KeyValue(Key {
442 partition_id: Some(partition),
443 path,
444 })
445 }
446 _ => continue,
447 });
448 }
449 Ok(Value {
450 value_type,
451 ..Value::default()
452 })
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn an_entity_reads_back() {
461 let key = Key {
462 partition_id: Some(PartitionId {
463 project_id: "p".into(),
464 namespace_id: "ns".into(),
465 ..PartitionId::default()
466 }),
467 path: vec![PathElement {
468 kind: "T".into(),
469 id_type: Some(IdType::Id(7)),
470 }],
471 };
472 let mut properties = Properties::new();
473 properties.insert(
474 "s".into(),
475 Value {
476 value_type: Some(ValueType::StringValue("x".into())),
477 ..Value::default()
478 },
479 );
480 properties.insert(
481 "b".into(),
482 Value {
483 value_type: Some(ValueType::BlobValue(vec![1])),
484 exclude_from_indexes: true,
485 ..Value::default()
486 },
487 );
488 let (read_key, read) = decode(&encode(&key, &properties)).unwrap();
489 assert_eq!(read_key, key);
490 assert_eq!(read, properties);
491 }
492}