Skip to main content

loro_common/
lib.rs

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
27/// Unique id for each peer. It's a random u64 by default.
28pub type PeerID = u64;
29/// If it's the nth Op of a peer, the counter will be n.
30pub type Counter = i32;
31/// It's the [Lamport clock](https://en.wikipedia.org/wiki/Lamport_timestamp)
32pub type Lamport = u32;
33
34/// It's the unique ID of an Op represented by [PeerID] and [Counter].
35#[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/// It's the unique ID of an Op represented by [PeerID] and [Counter].
65#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
66pub struct CompactId {
67    pub peer: PeerID,
68    pub counter: NonMaxI32,
69}
70
71/// Return whether the given name is a valid root container name.
72pub 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/// It's the unique ID of an Op represented by [PeerID] and [Lamport] clock.
112/// It's used to define the total order of Ops.
113#[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/// It's the unique ID of an Op represented by [PeerID] and [Counter].
126#[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/// It's the unique ID of an Op represented by [PeerID], [Lamport] clock and [Counter].
161#[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/// [ContainerID] includes the Op's [ID] and the type. So it's impossible to have
169/// the same [ContainerID] with conflict [ContainerType].
170///
171/// This structure is really cheap to clone.
172///
173/// String representation:
174///
175/// - Root Container: `/<name>:<type>`
176/// - Normal Container: `<counter>@<client>:<type>`
177///
178/// Note: It will be encoded into binary format, so the order of its fields should not be changed.
179#[derive(Hash, PartialEq, Eq, Clone, Serialize, Deserialize, EnumAsInner)]
180pub enum ContainerID {
181    /// Root container does not need an op to create. It can be created implicitly.
182    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        // normal need 13 bytes
222        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// TODO: add non_exhausted
324// Note: It will be encoded into binary format, so the order of its fields should not be changed.
325#[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
409// For some historical reason, we have another to_byte format for ContainerType,
410// it was used for serde of ContainerType
411fn 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                // root container
547                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
661/// In movable tree, we use a specific [`TreeID`] to represent the root of **ALL** deleted tree node.
662///
663/// Deletion operation is equivalent to move target tree node to [`DELETED_TREE_ROOT`].
664pub const DELETED_TREE_ROOT: TreeID = TreeID {
665    peer: PeerID::MAX,
666    counter: Counter::MAX,
667};
668
669/// Each node of movable tree has a unique [`TreeID`] generated by Loro.
670///
671/// To further represent the metadata (a MapContainer) associated with each node,
672/// we also use [`TreeID`] as [`ID`] portion of [`ContainerID`].
673/// This not only allows for convenient association of metadata with each node,
674/// but also ensures the uniqueness of the MapContainer.
675///
676/// Special ID:
677/// - [`DELETED_TREE_ROOT`]: the root of all deleted nodes. To get it by [`TreeID::delete_root()`]
678#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
679
680pub struct TreeID {
681    pub peer: PeerID,
682    // TODO: can use a NonMax here
683    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    /// return [`DELETED_TREE_ROOT`]
693    pub const fn delete_root() -> Self {
694        DELETED_TREE_ROOT
695    }
696
697    /// return `true` if the `TreeID` is deleted root
698    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}