1use anybytes::{Bytes, View};
10use winnow::stream::Stream;
11
12use crate::blob::encodings::longstring::LongString;
13use crate::blob::Blob;
14use crate::blob::IntoBlob;
15use crate::id::{ExclusiveId, Id, RawId, ID_LEN};
16use crate::macros::{entity, id_hex};
17use crate::metadata;
18use crate::repo::BlobStore;
19use crate::trible::Fragment;
20use crate::trible::TribleSet;
21use crate::inline::encodings::boolean::Boolean;
22use crate::inline::encodings::genid::GenId;
23use crate::inline::encodings::hash::{Blake3, Handle};
24use crate::inline::encodings::iu256::U256BE;
25use crate::inline::Inline;
26use triblespace_core_macros::attributes;
27
28use crate::import::json::{
29 parse_number_common, parse_string_common, parse_unicode_escape, EncodeError, JsonImportError,
30};
31
32type ParsedString = View<str>;
33
34attributes! {
35 "D78B9D5A96029FDBBB327E377418AF51" as pub kind: GenId;
37 "40BC51924FD5D2058A48D1FA6073F871" as pub string: Handle<LongString>;
39 "428E02672FFD0D010D95AE641ADE1730" as pub number_raw: Handle<LongString>;
41 "6F43FC771207574BF4CC58D3080C313C" as pub boolean: Boolean;
43 "97A4ACD83EC9EA29EE7E487BB058C437" as pub field_parent: GenId;
45 "2B9FCF2A60C9B05FADDA9F022762B822" as pub field_name: Handle<LongString>;
47 "38C7B1CDEA580DE70A520B2C8CBC4F14" as pub field_index: U256BE;
49 "6E6CA175F925B6AA0844D357B409F15A" as pub field_value: GenId;
51 "B49E6499D0A2CF5DD9A1E72D9D047747" as pub array_parent: GenId;
53 "D5DA41A093BD0DE490925126D1150B57" as pub array_index: U256BE;
55 "33535F41827B476B1EC0CACECE9BEED0" as pub array_value: GenId;
57}
58
59#[allow(non_upper_case_globals)]
61pub const kind_object: Id = id_hex!("64D8981414502BF750387C617F1F9D09");
62#[allow(non_upper_case_globals)]
64pub const kind_array: Id = id_hex!("5DC7096A184E658C8E16C54EB207C386");
65#[allow(non_upper_case_globals)]
67pub const kind_string: Id = id_hex!("58A5EAC244801C5E26AD9178C784781A");
68#[allow(non_upper_case_globals)]
70pub const kind_number: Id = id_hex!("711555ADF72B9499E6A7F68E0BD3B4B8");
71#[allow(non_upper_case_globals)]
73pub const kind_bool: Id = id_hex!("7D3079C5E20658B6CA5F54771B5D0D30");
74#[allow(non_upper_case_globals)]
76pub const kind_null: Id = id_hex!("FC1DCF98A3A8418D6090EBD367CFFD7A");
77#[allow(non_upper_case_globals)]
79pub const kind_field: Id = id_hex!("890FC1F34B9FAD18F93E6EDF1B69A1A2");
80#[allow(non_upper_case_globals)]
82pub const kind_array_entry: Id = id_hex!("EB325EABEA8C35DE7E5D700A5EF9207B");
83
84pub fn build_json_tree_metadata() -> Fragment {
87 let mut metadata = describe();
96
97 metadata += describe_kind(kind_object, "json.kind.object", "JSON object node.");
98 metadata += describe_kind(kind_array, "json.kind.array", "JSON array node.");
99 metadata += describe_kind(kind_string, "json.kind.string", "JSON string node.");
100 metadata += describe_kind(kind_number, "json.kind.number", "JSON number node.");
101 metadata += describe_kind(kind_bool, "json.kind.bool", "JSON boolean node.");
102 metadata += describe_kind(kind_null, "json.kind.null", "JSON null node.");
103 metadata += describe_kind(
104 kind_field,
105 "json.kind.field",
106 "JSON object field entry.",
107 );
108 metadata += describe_kind(
109 kind_array_entry,
110 "json.kind.array_entry",
111 "JSON array entry.",
112 );
113
114 metadata
115}
116
117fn describe_kind(kind_id: Id, name: &str, description: &str) -> Fragment {
118 entity! { ExclusiveId::force_ref(&kind_id) @
119 metadata::name: name.to_owned(),
120 metadata::description: description.to_owned(),
121 }
122}
123
124#[derive(Clone)]
125struct FieldEntry {
126 name: View<str>,
127 name_handle: Inline<Handle<LongString>>,
128 index: u64,
129 value: Id,
130}
131
132#[derive(Clone)]
133struct ArrayEntry {
134 index: u64,
135 value: Id,
136}
137
138pub struct JsonTreeImporter<'a, Store>
143where
144 Store: BlobStore,
145{
146 store: &'a mut Store,
147 id_salt: Option<[u8; 32]>,
148}
149
150impl<'a, Store> JsonTreeImporter<'a, Store>
151where
152 Store: BlobStore,
153{
154 pub fn new(store: &'a mut Store, id_salt: Option<[u8; 32]>) -> Self {
157 Self {
158 store,
159 id_salt,
160 }
161 }
162
163 pub fn import_str(&mut self, input: &str) -> Result<Fragment, JsonImportError> {
165 self.import_blob(input.to_owned().to_blob())
166 }
167
168 pub fn import_blob(&mut self, blob: Blob<LongString>) -> Result<Fragment, JsonImportError> {
171 let mut data = TribleSet::new();
172 let mut bytes = blob.bytes.clone();
173 self.skip_ws(&mut bytes);
174 let root = self.parse_value(&mut bytes, &mut data)?;
175 self.skip_ws(&mut bytes);
176 if bytes.peek_token().is_some() {
177 return Err(JsonImportError::Syntax("trailing tokens".into()));
178 }
179 Ok(Fragment::rooted(root, data))
180 }
181
182 pub fn metadata(&self) -> Fragment {
185 build_json_tree_metadata()
186 }
187
188 fn parse_value(
189 &mut self,
190 bytes: &mut Bytes,
191 data: &mut TribleSet,
192 ) -> Result<Id, JsonImportError> {
193 match bytes.peek_token() {
194 Some(b'n') => {
195 self.consume_literal(bytes, b"null")?;
196 let id = self.hash_tagged(b"null", &[]);
197 *data += entity! { ExclusiveId::force_ref(&id) @
198 kind: kind_null,
199 };
200 Ok(id)
201 }
202 Some(b't') => {
203 self.consume_literal(bytes, b"true")?;
204 let id = self.hash_tagged(b"bool", &[b"true"]);
205 *data += entity! { ExclusiveId::force_ref(&id) @
206 kind: kind_bool,
207 boolean: true,
208 };
209 Ok(id)
210 }
211 Some(b'f') => {
212 self.consume_literal(bytes, b"false")?;
213 let id = self.hash_tagged(b"bool", &[b"false"]);
214 *data += entity! { ExclusiveId::force_ref(&id) @
215 kind: kind_bool,
216 boolean: false,
217 };
218 Ok(id)
219 }
220 Some(b'"') => {
221 let text = self.parse_string(bytes)?;
222 let id = self.hash_tagged(b"string", &[text.as_ref().as_bytes()]);
223 let handle = self
224 .store
225 .put(text)
226 .map_err(|err| JsonImportError::EncodeString {
227 field: "string".to_string(),
228 source: EncodeError::from_error(err),
229 })?;
230 *data += entity! { ExclusiveId::force_ref(&id) @
231 kind: kind_string,
232 string: handle,
233 };
234 Ok(id)
235 }
236 Some(b'{') => self.parse_object(bytes, data),
237 Some(b'[') => self.parse_array(bytes, data),
238 _ => {
239 let number = self.parse_number(bytes)?;
240 let number_view = number
241 .view::<str>()
242 .map_err(|_| JsonImportError::Syntax("invalid number".into()))?;
243 let id = self.hash_tagged(b"number", &[number_view.as_ref().as_bytes()]);
244 let handle =
245 self.store
246 .put(number_view)
247 .map_err(|err| JsonImportError::EncodeNumber {
248 field: "number".to_string(),
249 source: EncodeError::from_error(err),
250 })?;
251 *data += entity! { ExclusiveId::force_ref(&id) @
252 kind: kind_number,
253 number_raw: handle,
254 };
255 Ok(id)
256 }
257 }
258 }
259
260 fn parse_object(
261 &mut self,
262 bytes: &mut Bytes,
263 data: &mut TribleSet,
264 ) -> Result<Id, JsonImportError> {
265 self.consume_byte(bytes, b'{')?;
266 self.skip_ws(bytes);
267
268 let mut fields: Vec<FieldEntry> = Vec::new();
269 if bytes.peek_token() == Some(b'}') {
270 self.consume_byte(bytes, b'}')?;
271 } else {
272 let mut index: u64 = 0;
273 loop {
274 let name = self.parse_string(bytes)?;
275 self.skip_ws(bytes);
276 self.consume_byte(bytes, b':')?;
277 self.skip_ws(bytes);
278 let value = self.parse_value(bytes, data)?;
279 let name_handle =
280 self.store
281 .put(name.clone())
282 .map_err(|err| JsonImportError::EncodeString {
283 field: "field".to_string(),
284 source: EncodeError::from_error(err),
285 })?;
286 fields.push(FieldEntry {
287 name,
288 name_handle,
289 index,
290 value,
291 });
292 index = index.saturating_add(1);
293
294 self.skip_ws(bytes);
295 match bytes.peek_token() {
296 Some(b',') => {
297 self.consume_byte(bytes, b',')?;
298 self.skip_ws(bytes);
299 }
300 Some(b'}') => {
301 self.consume_byte(bytes, b'}')?;
302 break;
303 }
304 _ => return Err(JsonImportError::Syntax("unexpected token".into())),
305 }
306 }
307 }
308
309 let object_id = self.hash_object(&fields);
310 *data += entity! { ExclusiveId::force_ref(&object_id) @
311 kind: kind_object,
312 };
313
314 for field in fields {
315 let entry_id = self.hash_field_entry(&object_id, &field);
316 *data += entity! { ExclusiveId::force_ref(&entry_id) @
317 kind: kind_field,
318 field_parent: object_id,
319 field_name: field.name_handle,
320 field_index: field.index,
321 field_value: field.value,
322 };
323 }
324
325 Ok(object_id)
326 }
327
328 fn parse_array(
329 &mut self,
330 bytes: &mut Bytes,
331 data: &mut TribleSet,
332 ) -> Result<Id, JsonImportError> {
333 self.consume_byte(bytes, b'[')?;
334 self.skip_ws(bytes);
335
336 let mut entries: Vec<ArrayEntry> = Vec::new();
337 if bytes.peek_token() == Some(b']') {
338 self.consume_byte(bytes, b']')?;
339 } else {
340 let mut index: u64 = 0;
341 loop {
342 let value = self.parse_value(bytes, data)?;
343 entries.push(ArrayEntry { index, value });
344 index = index.saturating_add(1);
345
346 self.skip_ws(bytes);
347 match bytes.peek_token() {
348 Some(b',') => {
349 self.consume_byte(bytes, b',')?;
350 self.skip_ws(bytes);
351 }
352 Some(b']') => {
353 self.consume_byte(bytes, b']')?;
354 break;
355 }
356 _ => return Err(JsonImportError::Syntax("unexpected token".into())),
357 }
358 }
359 }
360
361 let array_id = self.hash_array(&entries);
362 *data += entity! { ExclusiveId::force_ref(&array_id) @
363 kind: kind_array,
364 };
365
366 for entry in entries {
367 let entry_id = self.hash_array_entry(&array_id, &entry);
368 *data += entity! { ExclusiveId::force_ref(&entry_id) @
369 kind: kind_array_entry,
370 array_parent: array_id,
371 array_index: entry.index,
372 array_value: entry.value,
373 };
374 }
375
376 Ok(array_id)
377 }
378
379 fn hash_object(&self, fields: &[FieldEntry]) -> Id {
380 let mut hasher = self.seeded_hasher();
381 hash_chunk(&mut hasher, b"object");
382 for field in fields {
383 let index_bytes = field.index.to_be_bytes();
384 hash_chunk(&mut hasher, field.name.as_ref().as_bytes());
385 hash_chunk(&mut hasher, &index_bytes);
386 hash_chunk(&mut hasher, field.value.as_ref());
387 }
388 self.finish_hash(hasher)
389 }
390
391 fn hash_array(&self, entries: &[ArrayEntry]) -> Id {
392 let mut hasher = self.seeded_hasher();
393 hash_chunk(&mut hasher, b"array");
394 for entry in entries {
395 let index_bytes = entry.index.to_be_bytes();
396 hash_chunk(&mut hasher, &index_bytes);
397 hash_chunk(&mut hasher, entry.value.as_ref());
398 }
399 self.finish_hash(hasher)
400 }
401
402 fn hash_field_entry(&self, parent: &Id, entry: &FieldEntry) -> Id {
403 let mut hasher = self.seeded_hasher();
404 hash_chunk(&mut hasher, b"field");
405 let index_bytes = entry.index.to_be_bytes();
406 hash_chunk(&mut hasher, parent.as_ref());
407 hash_chunk(&mut hasher, entry.name.as_ref().as_bytes());
408 hash_chunk(&mut hasher, &index_bytes);
409 hash_chunk(&mut hasher, entry.value.as_ref());
410 self.finish_hash(hasher)
411 }
412
413 fn hash_array_entry(&self, parent: &Id, entry: &ArrayEntry) -> Id {
414 let mut hasher = self.seeded_hasher();
415 hash_chunk(&mut hasher, b"array_entry");
416 let index_bytes = entry.index.to_be_bytes();
417 hash_chunk(&mut hasher, parent.as_ref());
418 hash_chunk(&mut hasher, &index_bytes);
419 hash_chunk(&mut hasher, entry.value.as_ref());
420 self.finish_hash(hasher)
421 }
422
423 fn hash_tagged(&self, tag: &[u8], parts: &[&[u8]]) -> Id {
424 let mut hasher = self.seeded_hasher();
425 hash_chunk(&mut hasher, tag);
426 for part in parts {
427 hash_chunk(&mut hasher, part);
428 }
429 self.finish_hash(hasher)
430 }
431
432 fn seeded_hasher(&self) -> Blake3 {
433 let mut hasher = Blake3::new();
434 if let Some(salt) = self.id_salt {
435 hasher.update(salt.as_ref());
436 }
437 hasher
438 }
439
440 fn finish_hash(&self, hasher: Blake3) -> Id {
441 let digest = hasher.finalize();
442 id_from_digest(digest.as_ref())
443 }
444
445 fn skip_ws(&self, bytes: &mut Bytes) {
446 while matches!(bytes.peek_token(), Some(b) if b.is_ascii_whitespace()) {
447 bytes.pop_front();
448 }
449 }
450
451 fn consume_byte(&self, bytes: &mut Bytes, expected: u8) -> Result<(), JsonImportError> {
452 match bytes.pop_front() {
453 Some(b) if b == expected => Ok(()),
454 _ => Err(JsonImportError::Syntax("unexpected token".into())),
455 }
456 }
457
458 fn consume_literal(&self, bytes: &mut Bytes, literal: &[u8]) -> Result<(), JsonImportError> {
459 for expected in literal {
460 self.consume_byte(bytes, *expected)?;
461 }
462 Ok(())
463 }
464
465 fn parse_string(&self, bytes: &mut Bytes) -> Result<ParsedString, JsonImportError> {
466 let raw = parse_string_common(bytes, &mut parse_unicode_escape)?;
467 raw.view::<str>()
468 .map_err(|_| JsonImportError::Syntax("invalid utf-8".into()))
469 }
470
471 fn parse_number(&self, bytes: &mut Bytes) -> Result<Bytes, JsonImportError> {
472 parse_number_common(bytes)
473 }
474}
475
476fn hash_chunk(hasher: &mut Blake3, bytes: &[u8]) {
477 let len = (bytes.len() as u64).to_be_bytes();
478 hasher.update(&len);
479 hasher.update(bytes);
480}
481
482fn id_from_digest(digest: &[u8]) -> Id {
483 let mut raw: RawId = [0u8; ID_LEN];
484 raw.copy_from_slice(&digest[digest.len() - ID_LEN..]);
485 if raw == [0; ID_LEN] {
486 raw[0] = 1;
487 }
488 Id::new(raw).unwrap_or_else(|| unsafe { Id::force(raw) })
489}
490
491#[cfg(test)]
492mod tests {
493 use super::{kind_array_entry, JsonTreeImporter};
494 use crate::blob::MemoryBlobStore;
495 use crate::blob::IntoBlob;
496 use crate::id::Id;
497 use crate::macros::{find, pattern};
498
499
500 #[test]
501 fn lossless_ids_are_content_based() {
502 let input = r#"{ "a": [1, 2] }"#;
503 let mut blobs = MemoryBlobStore::new();
504 let mut importer = JsonTreeImporter::<_>::new(&mut blobs, None);
505 let root = importer
506 .import_blob(input.to_blob())
507 .unwrap()
508 .root()
509 .expect("import_blob returns a rooted fragment");
510 drop(importer);
511 let mut other = JsonTreeImporter::<_>::new(&mut blobs, None);
512 let other_root = other
513 .import_blob(input.to_blob())
514 .unwrap()
515 .root()
516 .expect("import_blob returns a rooted fragment");
517 assert_eq!(root, other_root);
518 }
519
520 #[test]
521 fn lossless_preserves_array_order() {
522 let input = r#"[1, 2]"#;
523 let mut blobs = MemoryBlobStore::new();
524 let mut importer = JsonTreeImporter::<_>::new(&mut blobs, None);
525 let fragment = importer.import_blob(input.to_blob()).unwrap();
526 let root = fragment
527 .root()
528 .expect("import_blob returns a rooted fragment");
529 let catalog = fragment.facts();
530 let mut entries = find!(
531 (index: ethnum::U256, value: Id),
532 pattern!(catalog, [{
533 _?entry @
534 super::kind: kind_array_entry,
535 super::array_parent: root,
536 super::array_index: ?index,
537 super::array_value: ?value,
538 }])
539 )
540 .collect::<Vec<_>>();
541 entries.sort_by_key(|(index, _)| *index);
542 assert_eq!(entries.len(), 2);
543 assert_eq!(entries[0].0, ethnum::U256::new(0));
544 assert_eq!(entries[1].0, ethnum::U256::new(1));
545 }
546}