1use std::{fmt::Display, io::Write};
2
3use arbitrary::Arbitrary;
4use enum_as_inner::EnumAsInner;
5
6use nonmax::{NonMaxI32, NonMaxU32};
7use serde::{Deserialize, Serialize};
8
9mod error;
10mod id;
11mod internal_string;
12mod logging;
13mod macros;
14mod span;
15mod value;
16
17pub use error::{LoroEncodeError, LoroError, LoroResult, LoroTreeError};
18pub use internal_string::InternalString;
19pub use logging::log::*;
20#[doc(hidden)]
21pub use rustc_hash::FxHashMap;
22pub use span::*;
23pub use value::{
24 to_value, LoroBinaryValue, LoroListValue, LoroMapValue, LoroStringValue, LoroValue,
25};
26
27pub type PeerID = u64;
29pub type Counter = i32;
31pub type Lamport = u32;
33
34#[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
36pub struct ID {
37 pub peer: PeerID,
38 pub counter: Counter,
39}
40
41impl ID {
42 pub fn to_bytes(&self) -> [u8; 12] {
43 let mut bytes = [0; 12];
44 bytes[..8].copy_from_slice(&self.peer.to_be_bytes());
45 bytes[8..].copy_from_slice(&self.counter.to_be_bytes());
46 bytes
47 }
48
49 pub fn from_bytes(bytes: &[u8]) -> Self {
50 if bytes.len() != 12 {
51 panic!(
52 "Invalid ID bytes. Expected 12 bytes but got {} bytes",
53 bytes.len()
54 );
55 }
56
57 Self {
58 peer: u64::from_be_bytes(bytes[..8].try_into().unwrap()),
59 counter: i32::from_be_bytes(bytes[8..].try_into().unwrap()),
60 }
61 }
62}
63
64#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
66pub struct CompactId {
67 pub peer: PeerID,
68 pub counter: NonMaxI32,
69}
70
71pub fn check_root_container_name(name: &str) -> bool {
73 !name.is_empty() && name.char_indices().all(|(_, x)| x != '/' && x != '\0')
74}
75
76impl CompactId {
77 pub fn new(peer: PeerID, counter: Counter) -> Self {
78 Self {
79 peer,
80 counter: NonMaxI32::new(counter).unwrap(),
81 }
82 }
83
84 pub fn to_id(&self) -> ID {
85 ID {
86 peer: self.peer,
87 counter: self.counter.get(),
88 }
89 }
90
91 pub fn inc(&self, start: i32) -> CompactId {
92 Self {
93 peer: self.peer,
94 counter: NonMaxI32::new(start + self.counter.get()).unwrap(),
95 }
96 }
97}
98
99impl TryFrom<ID> for CompactId {
100 type Error = ID;
101
102 fn try_from(id: ID) -> Result<Self, ID> {
103 if id.counter == i32::MAX {
104 return Err(id);
105 }
106
107 Ok(Self::new(id.peer, id.counter))
108 }
109}
110
111#[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord)]
114pub struct IdLp {
115 pub lamport: Lamport,
116 pub peer: PeerID,
117}
118
119impl IdLp {
120 pub fn compact(self) -> CompactIdLp {
121 CompactIdLp::new(self.peer, self.lamport)
122 }
123}
124
125#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
127pub struct CompactIdLp {
128 pub peer: PeerID,
129 pub lamport: NonMaxU32,
130}
131
132impl CompactIdLp {
133 pub fn new(peer: PeerID, lamport: Lamport) -> Self {
134 Self {
135 peer,
136 lamport: NonMaxU32::new(lamport).unwrap(),
137 }
138 }
139
140 pub fn to_id(&self) -> IdLp {
141 IdLp {
142 peer: self.peer,
143 lamport: self.lamport.get(),
144 }
145 }
146}
147
148impl TryFrom<IdLp> for CompactIdLp {
149 type Error = IdLp;
150
151 fn try_from(id: IdLp) -> Result<Self, IdLp> {
152 if id.lamport == u32::MAX {
153 return Err(id);
154 }
155
156 Ok(Self::new(id.peer, id.lamport))
157 }
158}
159
160#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
162pub struct IdFull {
163 pub peer: PeerID,
164 pub lamport: Lamport,
165 pub counter: Counter,
166}
167
168#[derive(Hash, PartialEq, Eq, Clone, Serialize, Deserialize, EnumAsInner)]
180pub enum ContainerID {
181 Root {
183 name: InternalString,
184 container_type: ContainerType,
185 },
186 Normal {
187 peer: PeerID,
188 counter: Counter,
189 container_type: ContainerType,
190 },
191}
192
193impl ContainerID {
194 pub fn encode<W: Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
195 match self {
196 Self::Root {
197 name,
198 container_type,
199 } => {
200 let first_byte = container_type.to_u8() | 0b10000000;
201 writer.write_all(&[first_byte])?;
202 leb128::write::unsigned(writer, name.len() as u64)?;
203 writer.write_all(name.as_bytes())?;
204 }
205 Self::Normal {
206 peer,
207 counter,
208 container_type,
209 } => {
210 let first_byte = container_type.to_u8();
211 writer.write_all(&[first_byte])?;
212 writer.write_all(&peer.to_le_bytes())?;
213 writer.write_all(&counter.to_le_bytes())?;
214 }
215 }
216
217 Ok(())
218 }
219
220 pub fn to_bytes(&self) -> Vec<u8> {
221 let mut bytes = Vec::with_capacity(13);
223 self.encode(&mut bytes).unwrap();
224 bytes
225 }
226
227 pub fn from_bytes(bytes: &[u8]) -> Self {
228 Self::try_from_bytes(bytes).unwrap()
229 }
230
231 pub fn try_from_bytes(bytes: &[u8]) -> LoroResult<Self> {
232 if bytes.is_empty() {
233 return Err(LoroError::DecodeError(
234 "Decode container id failed".to_string().into_boxed_str(),
235 ));
236 }
237
238 let first_byte = bytes[0];
239 let container_type = ContainerType::try_from_u8(first_byte & 0b01111111)?;
240 let is_root = (first_byte & 0b10000000) != 0;
241
242 let mut reader = &bytes[1..];
243 match is_root {
244 true => {
245 let name_len = leb128::read::unsigned(&mut reader).map_err(|_| {
246 LoroError::DecodeError(
247 "Decode container id failed".to_string().into_boxed_str(),
248 )
249 })?;
250 let name_len = usize::try_from(name_len).map_err(|_| {
251 LoroError::DecodeError(
252 "Decode container id failed".to_string().into_boxed_str(),
253 )
254 })?;
255 if reader.len() != name_len {
256 return Err(LoroError::DecodeError(
257 "Decode container id failed".to_string().into_boxed_str(),
258 ));
259 }
260
261 let name = std::str::from_utf8(&reader[..name_len]).map_err(|_| {
262 LoroError::DecodeError(
263 "Decode container id failed".to_string().into_boxed_str(),
264 )
265 })?;
266 Ok(Self::Root {
267 name: InternalString::from(name),
268 container_type,
269 })
270 }
271 false => {
272 if reader.len() != 12 {
273 return Err(LoroError::DecodeError(
274 "Decode container id failed".to_string().into_boxed_str(),
275 ));
276 }
277
278 let peer = PeerID::from_le_bytes(reader[..8].try_into().unwrap());
279 let counter = i32::from_le_bytes(reader[8..12].try_into().unwrap());
280 Ok(Self::Normal {
281 peer,
282 counter,
283 container_type,
284 })
285 }
286 }
287 }
288
289 const LORO_CONTAINER_ID_PREFIX: &str = "🦜:";
290 pub fn to_loro_value_string(&self) -> String {
291 format!("{}{}", Self::LORO_CONTAINER_ID_PREFIX, self)
292 }
293
294 pub fn try_from_loro_value_string(s: &str) -> Option<Self> {
295 if let Some(s) = s.strip_prefix(Self::LORO_CONTAINER_ID_PREFIX) {
296 Self::try_from(s).ok()
297 } else {
298 None
299 }
300 }
301}
302
303impl std::fmt::Debug for ContainerID {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 match self {
306 Self::Root {
307 name,
308 container_type,
309 } => {
310 write!(f, "Root(\"{name}\" {container_type:?})")
311 }
312 Self::Normal {
313 peer,
314 counter,
315 container_type,
316 } => {
317 write!(f, "Normal({container_type:?} {counter}@{peer})")
318 }
319 }
320 }
321}
322
323#[derive(Arbitrary, Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
326pub enum ContainerType {
327 Text,
328 Map,
329 List,
330 MovableList,
331 Tree,
332 #[cfg(feature = "counter")]
333 Counter,
334 Unknown(u8),
335}
336
337impl ContainerType {
338 #[cfg(feature = "counter")]
339 pub const ALL_TYPES: [ContainerType; 6] = [
340 ContainerType::Map,
341 ContainerType::List,
342 ContainerType::Text,
343 ContainerType::Tree,
344 ContainerType::MovableList,
345 ContainerType::Counter,
346 ];
347 #[cfg(not(feature = "counter"))]
348 pub const ALL_TYPES: [ContainerType; 5] = [
349 ContainerType::Map,
350 ContainerType::List,
351 ContainerType::Text,
352 ContainerType::Tree,
353 ContainerType::MovableList,
354 ];
355
356 pub fn default_value(&self) -> LoroValue {
357 match self {
358 ContainerType::Map => LoroValue::Map(Default::default()),
359 ContainerType::List => LoroValue::List(Default::default()),
360 ContainerType::Text => LoroValue::String(Default::default()),
361 ContainerType::Tree => LoroValue::List(Default::default()),
362 ContainerType::MovableList => LoroValue::List(Default::default()),
363 #[cfg(feature = "counter")]
364 ContainerType::Counter => LoroValue::Double(0.),
365 ContainerType::Unknown(_) => unreachable!(),
366 }
367 }
368
369 pub fn to_u8(self) -> u8 {
370 match self {
371 ContainerType::Map => 0,
372 ContainerType::List => 1,
373 ContainerType::Text => 2,
374 ContainerType::Tree => 3,
375 ContainerType::MovableList => 4,
376 #[cfg(feature = "counter")]
377 ContainerType::Counter => 5,
378 ContainerType::Unknown(k) => k,
379 }
380 }
381
382 pub fn try_from_u8(v: u8) -> LoroResult<Self> {
383 match v {
384 0 => Ok(ContainerType::Map),
385 1 => Ok(ContainerType::List),
386 2 => Ok(ContainerType::Text),
387 3 => Ok(ContainerType::Tree),
388 4 => Ok(ContainerType::MovableList),
389 #[cfg(feature = "counter")]
390 5 => Ok(ContainerType::Counter),
391 x => Ok(ContainerType::Unknown(x)),
392 }
393 }
394}
395
396#[derive(Serialize, Deserialize)]
397#[serde(rename = "ContainerType")]
398enum ContainerTypeSerdeRepr {
399 Text,
400 Map,
401 List,
402 MovableList,
403 Tree,
404 #[cfg(feature = "counter")]
405 Counter,
406 Unknown(u8),
407}
408
409fn historical_container_type_to_byte(c: ContainerType) -> u8 {
412 match c {
413 ContainerType::Text => 0,
414 ContainerType::Map => 1,
415 ContainerType::List => 2,
416 ContainerType::MovableList => 3,
417 ContainerType::Tree => 4,
418 #[cfg(feature = "counter")]
419 ContainerType::Counter => 5,
420 ContainerType::Unknown(k) => k,
421 }
422}
423
424fn historical_try_byte_to_container(byte: u8) -> ContainerType {
425 match byte {
426 0 => ContainerType::Text,
427 1 => ContainerType::Map,
428 2 => ContainerType::List,
429 3 => ContainerType::MovableList,
430 4 => ContainerType::Tree,
431 #[cfg(feature = "counter")]
432 5 => ContainerType::Counter,
433 _ => ContainerType::Unknown(byte),
434 }
435}
436
437impl From<ContainerType> for ContainerTypeSerdeRepr {
438 fn from(value: ContainerType) -> Self {
439 match value {
440 ContainerType::Text => Self::Text,
441 ContainerType::Map => Self::Map,
442 ContainerType::List => Self::List,
443 ContainerType::MovableList => Self::MovableList,
444 ContainerType::Tree => Self::Tree,
445 #[cfg(feature = "counter")]
446 ContainerType::Counter => Self::Counter,
447 ContainerType::Unknown(value) => Self::Unknown(value),
448 }
449 }
450}
451
452impl From<ContainerTypeSerdeRepr> for ContainerType {
453 fn from(value: ContainerTypeSerdeRepr) -> Self {
454 match value {
455 ContainerTypeSerdeRepr::Text => ContainerType::Text,
456 ContainerTypeSerdeRepr::Map => ContainerType::Map,
457 ContainerTypeSerdeRepr::List => ContainerType::List,
458 ContainerTypeSerdeRepr::MovableList => ContainerType::MovableList,
459 ContainerTypeSerdeRepr::Tree => ContainerType::Tree,
460 #[cfg(feature = "counter")]
461 ContainerTypeSerdeRepr::Counter => ContainerType::Counter,
462 ContainerTypeSerdeRepr::Unknown(value) => ContainerType::Unknown(value),
463 }
464 }
465}
466
467impl Serialize for ContainerType {
468 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
469 where
470 S: serde::Serializer,
471 {
472 if serializer.is_human_readable() {
473 ContainerTypeSerdeRepr::from(*self).serialize(serializer)
474 } else {
475 serializer.serialize_u8(historical_container_type_to_byte(*self))
476 }
477 }
478}
479
480impl<'de> Deserialize<'de> for ContainerType {
481 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
482 where
483 D: serde::Deserializer<'de>,
484 {
485 if deserializer.is_human_readable() {
486 let repr = ContainerTypeSerdeRepr::deserialize(deserializer)?;
487 Ok(repr.into())
488 } else {
489 let value = u8::deserialize(deserializer)?;
490 Ok(historical_try_byte_to_container(value))
491 }
492 }
493}
494
495pub type IdSpanVector = rustc_hash::FxHashMap<PeerID, CounterSpan>;
496
497mod container {
498 use super::*;
499
500 impl Display for ContainerType {
501 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502 f.write_str(match self {
503 ContainerType::Map => "Map",
504 ContainerType::List => "List",
505 ContainerType::MovableList => "MovableList",
506 ContainerType::Text => "Text",
507 ContainerType::Tree => "Tree",
508 #[cfg(feature = "counter")]
509 ContainerType::Counter => "Counter",
510 ContainerType::Unknown(k) => return f.write_fmt(format_args!("Unknown({k})")),
511 })
512 }
513 }
514
515 impl Display for ContainerID {
516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517 match self {
518 ContainerID::Root {
519 name,
520 container_type,
521 } => f.write_fmt(format_args!("cid:root-{name}:{container_type}"))?,
522 ContainerID::Normal {
523 peer,
524 counter,
525 container_type,
526 } => f.write_fmt(format_args!(
527 "cid:{id}:{container_type}",
528 id = ID::new(*peer, *counter),
529 container_type = container_type
530 ))?,
531 };
532 Ok(())
533 }
534 }
535
536 impl TryFrom<&str> for ContainerID {
537 type Error = ();
538
539 fn try_from(mut s: &str) -> Result<Self, Self::Error> {
540 if !s.starts_with("cid:") {
541 return Err(());
542 }
543
544 s = &s[4..];
545 if s.starts_with("root-") {
546 s = &s[5..];
548 let split = s.rfind(':').ok_or(())?;
549 if split == 0 {
550 return Err(());
551 }
552 let kind = ContainerType::try_from(&s[split + 1..]).map_err(|_| ())?;
553 let name = &s[..split];
554 Ok(ContainerID::Root {
555 name: name.into(),
556 container_type: kind,
557 })
558 } else {
559 let mut iter = s.split(':');
560 let id = iter.next().ok_or(())?;
561 let kind = iter.next().ok_or(())?;
562 if iter.next().is_some() {
563 return Err(());
564 }
565
566 let id = ID::try_from(id).map_err(|_| ())?;
567 let kind = ContainerType::try_from(kind).map_err(|_| ())?;
568 Ok(ContainerID::Normal {
569 peer: id.peer,
570 counter: id.counter,
571 container_type: kind,
572 })
573 }
574 }
575 }
576
577 impl ContainerID {
578 #[inline]
579 pub fn new_normal(id: ID, container_type: ContainerType) -> Self {
580 ContainerID::Normal {
581 peer: id.peer,
582 counter: id.counter,
583 container_type,
584 }
585 }
586
587 #[inline]
588 pub fn new_root(name: &str, container_type: ContainerType) -> Self {
589 if !check_root_container_name(name) {
590 panic!(
591 "Invalid root container name, it should not be empty or contain '/' or '\\0'"
592 );
593 } else {
594 ContainerID::Root {
595 name: name.into(),
596 container_type,
597 }
598 }
599 }
600
601 #[inline]
602 pub fn name(&self) -> &InternalString {
603 match self {
604 ContainerID::Root { name, .. } => name,
605 ContainerID::Normal { .. } => unreachable!(),
606 }
607 }
608
609 #[inline]
610 pub fn container_type(&self) -> ContainerType {
611 match self {
612 ContainerID::Root { container_type, .. } => *container_type,
613 ContainerID::Normal { container_type, .. } => *container_type,
614 }
615 }
616
617 pub fn is_unknown(&self) -> bool {
618 matches!(self.container_type(), ContainerType::Unknown(_))
619 }
620 }
621
622 impl TryFrom<&str> for ContainerType {
623 type Error = LoroError;
624
625 fn try_from(value: &str) -> Result<Self, Self::Error> {
626 match value {
627 "Map" | "map" => Ok(ContainerType::Map),
628 "List" | "list" => Ok(ContainerType::List),
629 "Text" | "text" => Ok(ContainerType::Text),
630 "Tree" | "tree" => Ok(ContainerType::Tree),
631 "MovableList" | "movableList" => Ok(ContainerType::MovableList),
632 #[cfg(feature = "counter")]
633 "Counter" | "counter" => Ok(ContainerType::Counter),
634 a => {
635 if a.ends_with(')') {
636 let start = a.find('(').ok_or_else(|| {
637 LoroError::DecodeError(
638 format!("Invalid container type string \"{value}\"").into(),
639 )
640 })?;
641 let k = a[start+1..a.len() - 1].parse().map_err(|_| {
642 LoroError::DecodeError(
643 format!("Unknown container type \"{value}\". The valid options are Map|List|Text|Tree|MovableList.").into(),
644 )
645 })?;
646 match ContainerType::try_from_u8(k) {
647 Ok(k) => Ok(k),
648 Err(_) => Ok(ContainerType::Unknown(k)),
649 }
650 } else {
651 Err(LoroError::DecodeError(
652 format!("Unknown container type \"{value}\". The valid options are Map|List|Text|Tree|MovableList.").into(),
653 ))
654 }
655 }
656 }
657 }
658 }
659}
660
661pub const DELETED_TREE_ROOT: TreeID = TreeID {
665 peer: PeerID::MAX,
666 counter: Counter::MAX,
667};
668
669#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
679
680pub struct TreeID {
681 pub peer: PeerID,
682 pub counter: Counter,
684}
685
686impl TreeID {
687 #[inline(always)]
688 pub fn new(peer: PeerID, counter: Counter) -> Self {
689 Self { peer, counter }
690 }
691
692 pub const fn delete_root() -> Self {
694 DELETED_TREE_ROOT
695 }
696
697 pub fn is_deleted_root(&self) -> bool {
699 self == &DELETED_TREE_ROOT
700 }
701
702 pub fn from_id(id: ID) -> Self {
703 Self {
704 peer: id.peer,
705 counter: id.counter,
706 }
707 }
708
709 pub fn id(&self) -> ID {
710 ID {
711 peer: self.peer,
712 counter: self.counter,
713 }
714 }
715
716 pub fn associated_meta_container(&self) -> ContainerID {
717 ContainerID::new_normal(self.id(), ContainerType::Map)
718 }
719}
720
721impl Display for TreeID {
722 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
723 self.id().fmt(f)
724 }
725}
726
727impl TryFrom<&str> for TreeID {
728 type Error = LoroError;
729 fn try_from(value: &str) -> Result<Self, Self::Error> {
730 let id = ID::try_from(value)?;
731 Ok(TreeID {
732 peer: id.peer,
733 counter: id.counter,
734 })
735 }
736}
737
738#[cfg(feature = "wasm")]
739pub mod wasm {
740 use crate::{LoroError, TreeID};
741 use wasm_bindgen::JsValue;
742 impl From<TreeID> for JsValue {
743 fn from(value: TreeID) -> Self {
744 JsValue::from_str(&format!("{value}"))
745 }
746 }
747
748 impl TryFrom<JsValue> for TreeID {
749 type Error = LoroError;
750 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
751 let id = value.as_string().unwrap();
752 TreeID::try_from(id.as_str())
753 }
754 }
755}
756
757#[cfg(test)]
758mod test {
759 use crate::{ContainerID, ContainerType, ID};
760
761 #[test]
762 fn test_container_id_convert_to_and_from_str() {
763 let id = ContainerID::Root {
764 name: "name".into(),
765 container_type: crate::ContainerType::Map,
766 };
767 let id_str = id.to_string();
768 assert_eq!(id_str.as_str(), "cid:root-name:Map");
769 assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
770
771 let id = ContainerID::Normal {
772 counter: 10,
773 peer: 255,
774 container_type: crate::ContainerType::Map,
775 };
776 let id_str = id.to_string();
777 assert_eq!(id_str.as_str(), "cid:10@255:Map");
778 assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
779
780 let id = ContainerID::try_from("cid:root-a:b:c:Tree").unwrap();
781 assert_eq!(
782 id,
783 ContainerID::new_root("a:b:c", crate::ContainerType::Tree)
784 );
785 }
786
787 #[test]
788 fn test_convert_invalid_container_id_str() {
789 assert!(ContainerID::try_from("cid:root-:Map").is_err());
790 assert!(ContainerID::try_from("cid:0@:Map").is_err());
791 assert!(ContainerID::try_from("cid:@:Map").is_err());
792 assert!(ContainerID::try_from("cid:x@0:Map").is_err());
793 assert!(ContainerID::try_from("id:0@0:Map").is_err());
794 assert!(ContainerID::try_from("cid:0@0:Unknown(6)").is_ok());
795 }
796
797 #[test]
798 fn test_container_id_encode_and_decode() {
799 let id = ContainerID::new_normal(ID::new(1, 2), ContainerType::Map);
800 let bytes = id.to_bytes();
801 assert_eq!(ContainerID::from_bytes(&bytes), id);
802
803 let id = ContainerID::new_normal(ID::new(u64::MAX, i32::MAX), ContainerType::Text);
804 let bytes = id.to_bytes();
805 assert_eq!(ContainerID::from_bytes(&bytes), id);
806
807 let id = ContainerID::new_root("test_root", ContainerType::List);
808 let bytes = id.to_bytes();
809 assert_eq!(ContainerID::from_bytes(&bytes), id);
810
811 let id = ContainerID::new_normal(ID::new(0, 0), ContainerType::MovableList);
812 let bytes = id.to_bytes();
813 assert_eq!(ContainerID::from_bytes(&bytes), id);
814
815 let id = ContainerID::new_root(&"x".repeat(1024), ContainerType::Tree);
816 let bytes = id.to_bytes();
817 assert_eq!(ContainerID::from_bytes(&bytes), id);
818
819 #[cfg(feature = "counter")]
820 {
821 let id = ContainerID::new_normal(ID::new(42, 100), ContainerType::Counter);
822 let bytes = id.to_bytes();
823 assert_eq!(ContainerID::from_bytes(&bytes), id);
824 }
825
826 let id = ContainerID::new_normal(ID::new(1, 1), ContainerType::Unknown(100));
827 let bytes = id.to_bytes();
828 assert_eq!(ContainerID::from_bytes(&bytes), id);
829 }
830
831 #[test]
832 fn container_id_try_from_bytes_rejects_trailing_bytes() {
833 let normal = ContainerID::new_normal(ID::new(1, 2), ContainerType::Map);
834 let mut normal_bytes = normal.to_bytes();
835 normal_bytes.push(0);
836 assert!(ContainerID::try_from_bytes(&normal_bytes).is_err());
837
838 let root = ContainerID::new_root("root", ContainerType::List);
839 let mut root_bytes = root.to_bytes();
840 root_bytes.push(0);
841 assert!(ContainerID::try_from_bytes(&root_bytes).is_err());
842 }
843}