1use super::{
20 ExportedDocument,
21 wire::{Field, Malformed, Reader, Writer},
22};
23use crate::{
24 order::{
25 VECTOR_TYPE, VECTOR_TYPE_KEY, VECTOR_VALUES_KEY, is_vector, numeric_id, vector_elements,
26 },
27 path::ResourcePath,
28 store::ReadTime,
29};
30use hidane_proto::google::{
31 firestore::v1::{ArrayValue, MapValue, Value, value::ValueType},
32 r#type::LatLng,
33};
34use hidane_proto::ordered::Fields;
35
36const DEFAULT_DATABASE: &str = "(default)";
37const VECTOR_PROPERTY: &str = "__vector__";
38
39const KEY: u32 = 13;
41const PROPERTY: u32 = 14;
42const RAW_PROPERTY: u32 = 15;
43const ENTITY_GROUP: u32 = 16;
44const APP: u32 = 13;
46const PATH: u32 = 14;
47const DATABASE_ID: u32 = 23;
48const ELEMENT: u32 = 1;
50const ELEMENT_TYPE: u32 = 2;
51const ELEMENT_ID: u32 = 3;
52const ELEMENT_NAME: u32 = 4;
53const MEANING: u32 = 1;
55const NAME: u32 = 3;
56const MULTIPLE: u32 = 4;
57const VALUE: u32 = 5;
58const INT64: u32 = 1;
60const BOOLEAN: u32 = 2;
61const STRING: u32 = 3;
62const DOUBLE: u32 = 4;
63const POINT: u32 = 5;
64const POINT_X: u32 = 6;
65const POINT_Y: u32 = 7;
66const REFERENCE: u32 = 12;
67const REFERENCE_APP: u32 = 13;
68const REFERENCE_ELEMENT: u32 = 14;
69const REFERENCE_TYPE: u32 = 15;
70const REFERENCE_ID: u32 = 16;
71const REFERENCE_NAME: u32 = 17;
72const REFERENCE_DATABASE_ID: u32 = 23;
73const GD_WHEN: u64 = 7;
75const GEORSS_POINT: u64 = 9;
76const BLOB: u64 = 14;
77const BYTESTRING: u64 = 16;
78const ENTITY_PROTO: u64 = 19;
79const EMPTY_LIST: u64 = 24;
80
81pub fn encode(project: &str, database_id: &str, path: &ResourcePath, fields: &Fields) -> Vec<u8> {
83 let segments: Vec<&str> = path.segments().collect();
84 let mut w = Writer::default();
85 w.message(KEY, |key| {
86 key.bytes(APP, format!("dev~{project}").as_bytes());
87 key.message(PATH, |p| {
88 for pair in segments.chunks(2) {
89 p.group(ELEMENT, |e| {
90 element(e, pair, ELEMENT_TYPE, ELEMENT_ID, ELEMENT_NAME)
91 });
92 }
93 });
94 if database_id != DEFAULT_DATABASE {
95 key.bytes(DATABASE_ID, database_id.as_bytes());
96 }
97 });
98 properties(&mut w, fields);
99 w.message(ENTITY_GROUP, |p| {
100 if let Some(root) = segments.chunks(2).next() {
101 p.group(ELEMENT, |e| {
102 element(e, root, ELEMENT_TYPE, ELEMENT_ID, ELEMENT_NAME)
103 });
104 }
105 });
106 w.into_bytes()
107}
108
109fn element(w: &mut Writer, pair: &[&str], kind: u32, id: u32, name: u32) {
111 w.bytes(kind, pair[0].as_bytes());
112 let doc = pair.get(1).copied().unwrap_or_default();
113 match numeric_id(doc) {
114 Some(n) => w.int(id, n),
115 None => w.bytes(name, doc.as_bytes()),
116 }
117}
118
119fn array(value: &Value) -> Option<&[Value]> {
120 match &value.value_type {
121 Some(ValueType::ArrayValue(array)) => Some(&array.values),
122 _ => None,
123 }
124}
125
126fn properties(w: &mut Writer, fields: &Fields) {
127 for (name, value) in fields {
128 if array(value).is_some_and(<[Value]>::is_empty) {
129 w.message(PROPERTY, |p| {
130 p.uint(MEANING, EMPTY_LIST);
131 p.bytes(NAME, name.as_bytes());
132 p.uint(MULTIPLE, 0);
133 p.message(VALUE, |_| {});
134 });
135 }
136 }
137 for (name, value) in fields {
138 match array(value) {
139 Some(values) => {
140 for element in values {
141 property(w, name, element, true);
142 }
143 }
144 None => property(w, name, value, false),
145 }
146 }
147}
148
149fn property(w: &mut Writer, name: &str, value: &Value, multiple: bool) {
150 let meaning = match &value.value_type {
151 Some(ValueType::TimestampValue(_)) => Some(GD_WHEN),
152 Some(ValueType::BytesValue(_)) => Some(BLOB),
153 Some(ValueType::GeoPointValue(_)) => Some(GEORSS_POINT),
154 Some(ValueType::MapValue(_)) => Some(ENTITY_PROTO),
155 _ => None,
156 };
157 w.message(RAW_PROPERTY, |p| {
158 if let Some(meaning) = meaning {
159 p.uint(MEANING, meaning);
160 }
161 p.bytes(NAME, name.as_bytes());
162 p.uint(MULTIPLE, u64::from(multiple));
163 p.message(VALUE, |v| property_value(v, value));
164 });
165}
166
167fn property_value(w: &mut Writer, value: &Value) {
168 match &value.value_type {
169 Some(ValueType::BooleanValue(b)) => w.uint(BOOLEAN, u64::from(*b)),
170 Some(ValueType::IntegerValue(i)) => w.int(INT64, *i),
171 Some(ValueType::DoubleValue(d)) => w.double(DOUBLE, if *d == 0.0 { 0.0 } else { *d }),
172 Some(ValueType::TimestampValue(ts)) => w.int(INT64, ReadTime::from_timestamp(ts).0),
173 Some(ValueType::StringValue(s)) => w.bytes(STRING, s.as_bytes()),
174 Some(ValueType::BytesValue(b)) => w.bytes(STRING, b),
175 Some(ValueType::ReferenceValue(name)) => reference(w, name),
176 Some(ValueType::GeoPointValue(point)) => w.group(POINT, |p| {
177 p.double(POINT_X, point.latitude);
178 p.double(POINT_Y, point.longitude);
179 }),
180 Some(ValueType::MapValue(map)) => w.bytes(STRING, &nested(map)),
181 _ => {}
183 }
184}
185
186fn reference(w: &mut Writer, name: &str) {
187 let Some((project, database, path)) = name
188 .strip_prefix("projects/")
189 .and_then(|rest| rest.split_once("/databases/"))
190 .and_then(|(project, rest)| {
191 let (database, path) = rest.split_once("/documents/")?;
192 Some((project, database, path))
193 })
194 else {
195 return;
196 };
197 let segments: Vec<&str> = path.split('/').collect();
198 w.group(REFERENCE, |r| {
199 r.bytes(REFERENCE_APP, format!("dev~{project}").as_bytes());
200 for pair in segments.chunks(2) {
201 r.group(REFERENCE_ELEMENT, |e| {
202 element(e, pair, REFERENCE_TYPE, REFERENCE_ID, REFERENCE_NAME);
203 });
204 }
205 if database != DEFAULT_DATABASE {
206 r.bytes(REFERENCE_DATABASE_ID, database.as_bytes());
207 }
208 });
209}
210
211fn nested(map: &MapValue) -> Vec<u8> {
213 let mut w = Writer::default();
214 w.message(KEY, |key| {
215 key.bytes(APP, b"");
216 key.message(PATH, |_| {});
217 });
218 if is_vector(map) {
219 for element in vector_elements(map) {
220 property(&mut w, VECTOR_PROPERTY, element, true);
221 }
222 } else {
223 properties(&mut w, &map.fields);
224 }
225 w.message(ENTITY_GROUP, |_| {});
226 w.into_bytes()
227}
228
229pub fn decode(bytes: &[u8]) -> Result<ExportedDocument, Malformed> {
232 let mut database_id = DEFAULT_DATABASE.to_owned();
233 let mut segments = None;
234 let mut fields = Fields::new();
235 for field in Reader::new(bytes) {
236 let (number, value) = field?;
237 match number {
238 KEY => {
239 let mut path = Vec::new();
240 for field in value.fields().ok_or(Malformed)? {
241 let (number, value) = field?;
242 match number {
243 PATH => {
244 for field in value.fields().ok_or(Malformed)? {
245 let (number, value) = field?;
246 if number == ELEMENT {
247 path.extend(decode_element(
248 value,
249 ELEMENT_TYPE,
250 ELEMENT_ID,
251 ELEMENT_NAME,
252 )?);
253 }
254 }
255 }
256 DATABASE_ID => database_id = text(value)?,
257 _ => {}
258 }
259 }
260 segments = Some(path);
261 }
262 PROPERTY => decode_property(&mut fields, value, Relative::Yes)?,
263 RAW_PROPERTY => decode_property(&mut fields, value, Relative::No)?,
264 _ => {}
265 }
266 }
267 let segments = segments.ok_or(Malformed)?;
268 if segments.is_empty() {
269 return Err(Malformed);
270 }
271 if database_id.is_empty() {
272 DEFAULT_DATABASE.clone_into(&mut database_id);
273 }
274 Ok(ExportedDocument {
275 database_id,
276 path: ResourcePath::from_segments(segments),
277 fields,
278 })
279}
280
281fn text(field: Field<'_>) -> Result<String, Malformed> {
282 String::from_utf8(field.as_bytes().ok_or(Malformed)?.to_vec()).map_err(|_| Malformed)
283}
284
285fn decode_element(
286 field: Field<'_>,
287 kind: u32,
288 id: u32,
289 name: u32,
290) -> Result<[String; 2], Malformed> {
291 let (mut collection, mut document) = (None, None);
292 for field in field.fields().ok_or(Malformed)? {
293 let (number, value) = field?;
294 if number == kind {
295 collection = Some(text(value)?);
296 } else if number == id {
297 #[allow(clippy::cast_possible_wrap)]
298 let n = value.as_varint().ok_or(Malformed)? as i64;
299 document = Some(format!("__id{n}__"));
300 } else if number == name {
301 document = Some(text(value)?);
302 }
303 }
304 Ok([collection.ok_or(Malformed)?, document.ok_or(Malformed)?])
305}
306
307#[derive(Clone, Copy, PartialEq, Eq)]
309enum Relative {
310 Yes,
311 No,
312}
313
314fn decode_property(
315 fields: &mut Fields,
316 field: Field<'_>,
317 relative: Relative,
318) -> Result<(), Malformed> {
319 let (mut meaning, mut name, mut multiple, mut value) = (0, None, false, &[][..]);
320 for field in field.fields().ok_or(Malformed)? {
321 let (number, field) = field?;
322 match number {
323 MEANING => meaning = field.as_varint().ok_or(Malformed)?,
324 NAME => name = Some(text(field)?),
325 MULTIPLE => multiple = field.as_varint().ok_or(Malformed)? != 0,
326 VALUE => value = field.as_bytes().ok_or(Malformed)?,
327 _ => {}
328 }
329 }
330 let name = name.ok_or(Malformed)?;
331 if meaning == EMPTY_LIST {
332 fields
333 .entry(name)
334 .or_insert_with(|| array_value(Vec::new()));
335 return Ok(());
336 }
337 let value = decode_value(value, meaning, relative)?;
338 if multiple {
339 let entry = fields
340 .entry(name)
341 .or_insert_with(|| array_value(Vec::new()));
342 match &mut entry.value_type {
343 Some(ValueType::ArrayValue(array)) => array.values.push(value),
344 _ => *entry = array_value(vec![value]),
345 }
346 } else {
347 fields.insert(name, value);
348 }
349 Ok(())
350}
351
352fn array_value(values: Vec<Value>) -> Value {
353 Value {
354 value_type: Some(ValueType::ArrayValue(ArrayValue { values })),
355 }
356}
357
358fn decode_value(bytes: &[u8], meaning: u64, relative: Relative) -> Result<Value, Malformed> {
359 let mut value_type = ValueType::NullValue(0);
360 for field in Reader::new(bytes) {
361 let (number, field) = field?;
362 value_type = match number {
363 INT64 => {
364 #[allow(clippy::cast_possible_wrap)]
365 let n = field.as_varint().ok_or(Malformed)? as i64;
366 if meaning == GD_WHEN {
367 ValueType::TimestampValue(ReadTime(n).to_timestamp())
368 } else {
369 ValueType::IntegerValue(n)
370 }
371 }
372 BOOLEAN => ValueType::BooleanValue(field.as_varint().ok_or(Malformed)? != 0),
373 STRING => {
374 let bytes = field.as_bytes().ok_or(Malformed)?;
375 match meaning {
376 BLOB | BYTESTRING => ValueType::BytesValue(bytes.to_vec()),
377 ENTITY_PROTO => return decode_nested(bytes),
378 _ => match String::from_utf8(bytes.to_vec()) {
379 Ok(s) => ValueType::StringValue(s),
380 Err(err) => ValueType::BytesValue(err.into_bytes()),
381 },
382 }
383 }
384 DOUBLE => ValueType::DoubleValue(f64::from_bits(field.as_fixed64().ok_or(Malformed)?)),
385 POINT => {
386 let mut point = LatLng::default();
387 for field in field.fields().ok_or(Malformed)? {
388 let (number, field) = field?;
389 let x = f64::from_bits(field.as_fixed64().ok_or(Malformed)?);
390 match number {
391 POINT_X => point.latitude = x,
392 POINT_Y => point.longitude = x,
393 _ => {}
394 }
395 }
396 ValueType::GeoPointValue(point)
397 }
398 REFERENCE => ValueType::ReferenceValue(decode_reference(field, relative)?),
399 _ => continue,
400 };
401 }
402 Ok(Value {
403 value_type: Some(value_type),
404 })
405}
406
407fn decode_reference(field: Field<'_>, relative: Relative) -> Result<String, Malformed> {
408 let (mut project, mut database, mut path) =
409 (String::new(), DEFAULT_DATABASE.to_owned(), Vec::new());
410 for field in field.fields().ok_or(Malformed)? {
411 let (number, field) = field?;
412 match number {
413 REFERENCE_APP => {
414 let app = text(field)?;
415 if relative == Relative::No {
417 project = app
418 .split_once('~')
419 .map_or(app.clone(), |(_, p)| p.to_owned());
420 }
421 }
422 REFERENCE_ELEMENT => path.extend(decode_element(
423 field,
424 REFERENCE_TYPE,
425 REFERENCE_ID,
426 REFERENCE_NAME,
427 )?),
428 REFERENCE_DATABASE_ID => {
429 let id = text(field)?;
430 if !id.is_empty() {
431 database = id;
432 }
433 }
434 _ => {}
435 }
436 }
437 Ok(format!(
438 "projects/{project}/databases/{database}/documents/{}",
439 path.join("/")
440 ))
441}
442
443fn decode_nested(bytes: &[u8]) -> Result<Value, Malformed> {
445 let mut fields = Fields::new();
446 for field in Reader::new(bytes) {
447 let (number, field) = field?;
448 if number == PROPERTY || number == RAW_PROPERTY {
449 decode_property(&mut fields, field, Relative::No)?;
450 }
451 }
452 if let Some(elements) = fields.remove(VECTOR_PROPERTY) {
453 let elements = match elements.value_type {
454 Some(ValueType::ArrayValue(_)) => elements,
455 _ => array_value(vec![elements]),
456 };
457 fields = Fields::from_iter([
458 (
459 VECTOR_TYPE_KEY.to_owned(),
460 Value {
461 value_type: Some(ValueType::StringValue(VECTOR_TYPE.to_owned())),
462 },
463 ),
464 (VECTOR_VALUES_KEY.to_owned(), elements),
465 ]);
466 }
467 Ok(Value {
468 value_type: Some(ValueType::MapValue(MapValue { fields })),
469 })
470}