dittolive_ditto/store/
document_id.rs

1use_prelude!();
2
3use crate::error::{DittoError, ErrorKind};
4
5#[derive(Debug, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
6/// An identifier for a document in a Ditto collection.
7///
8/// Each `DocumentId` represents a unique identifier for a document.
9pub struct DocumentId {
10    pub(crate) bytes: Vec<u8>,
11}
12
13impl DocumentId {
14    /// Create a new DocumentId from a serializable value.
15    /// # panic
16    /// This function will panic if the value can't be serialized into cbor.
17    pub fn new<V: ::serde::Serialize>(value: &V) -> Result<Self, DittoError> {
18        let cbor_bytes = ::serde_cbor::to_vec(value).unwrap();
19        let bytes = validate_doc_id_cbor_bytes(cbor_bytes)?;
20        Ok(Self { bytes })
21    }
22
23    /// Return a String representation of the DocumentId that can be used in a query string.
24    pub fn to_query_compatible(
25        &self,
26        string_primitive_fmt: ffi_sdk::StringPrimitiveFormat,
27    ) -> String {
28        let str_boxed = ffi_sdk::ditto_document_id_query_compatible(
29            self.bytes.as_slice().into(),
30            string_primitive_fmt,
31        );
32        str_boxed.into_string()
33    }
34
35    /// Return the inner bytes value in a cbor form
36    pub fn value(&self) -> ::serde_cbor::Value {
37        self.to_cbor()
38    }
39
40    /// Return the inner bytes value in a cbor form
41    pub fn to_cbor(&self) -> ::serde_cbor::Value {
42        ::serde_cbor::from_slice(&self.bytes[..]).expect("DocumentId can be represented as CBOR")
43    }
44}
45
46impl From<Vec<u8>> for DocumentId {
47    fn from(bytes: Vec<u8>) -> Self {
48        Self { bytes }
49    }
50}
51
52impl From<Box<[u8]>> for DocumentId {
53    fn from(bytes: Box<[u8]>) -> Self {
54        Self {
55            bytes: bytes.into(),
56        }
57    }
58}
59
60impl From<&[u8]> for DocumentId {
61    fn from(slice: &[u8]) -> Self {
62        Self {
63            bytes: slice.to_owned().to_vec(),
64        }
65    }
66}
67
68impl AsRef<[u8]> for DocumentId {
69    fn as_ref(&self) -> &[u8] {
70        &self.bytes[..]
71    }
72}
73
74impl std::fmt::Display for DocumentId {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        write!(
77            f,
78            "{}",
79            self.to_query_compatible(ffi_sdk::StringPrimitiveFormat::WithoutQuotes)
80        )
81    }
82}
83
84impl serde::Serialize for DocumentId {
85    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
86        // Note (Ham & Daniel): This serialization was previously done by first
87        // deserializing the document ID's CBOR bytes into a `serde_cbor::Value`
88        // before then serializing that into the serialized form. This was
89        // usually fine because the serializer being used to serialize the
90        // `serde_cbor::Value` was almost always `serde_cbor`'s, and so while a
91        // little bit of a roundabout method of getting back to some CBOR bytes,
92        // it always worked fine.
93        //
94        // However, if you wanted to `upsert` a document into Ditto by
95        // specifying the document's content using `serde_json`'s `json!` macro
96        // and you wanted to specify the document's ID by providing a
97        // `DocumentId` value under the `_id` at the root of the document then
98        // this codepath would get hit and would lead to an error if you were
99        // using a document ID that contained an integer value. This is because
100        // `serde_cbor::Value` has an `Integer` case that stores all integers as
101        // `i128`s. `serde_json` does not support serializing `i128`s and would
102        // return an error saying as much. It looks like you'd then be able to
103        // get this to work by enabling the `"arbitrary_precision"` feature of
104        // `serde_json`. This does mean that you don't get an error returned
105        // anymore but integers then end up getting serialized as an object
106        // like:
107        //
108        //   { "$serde_json::private::Number": 42 }
109        //
110        // This does not work well with in a cross-platform world unfortunately.
111        //
112        // As such, we're instead using `serde_transcode` for now, to avoid all
113        // integers getting temporarily represented as `i128`s. This may or may
114        // not end up being a suitable long term solution so is something that
115        // may need to be re-evaluated in the future.
116
117        let mut deserializer = serde_cbor::Deserializer::from_slice(&self.bytes[..]);
118        serde_transcode::transcode(&mut deserializer, serializer)
119    }
120}
121
122impl<'de> serde::de::Deserialize<'de> for DocumentId {
123    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124    where
125        D: serde::de::Deserializer<'de>,
126    {
127        let cbor_bytes =
128            serde_cbor::to_vec(&serde_cbor::Value::deserialize(deserializer)?).unwrap();
129        // We have to go to the trouble of crossing the FFI to ensure that the
130        // bytes we've got here are in Ditto document ID canonical CBOR form
131        let bytes = validate_doc_id_cbor_bytes(cbor_bytes)
132            .expect("document id bytes are valid when deserializing");
133        Ok(Self { bytes })
134    }
135}
136
137fn validate_doc_id_cbor_bytes(bytes: Vec<u8>) -> Result<Vec<u8>, DittoError> {
138    use safer_ffi::prelude::{AsOut, ManuallyDropMut};
139
140    let mut out_cbor_slot = None;
141    let out_cbor = out_cbor_slot.manually_drop_mut().as_out();
142    {
143        let res = ffi_sdk::ditto_validate_document_id(bytes[..].into(), out_cbor);
144        if res != 0 {
145            return Err(DittoError::from_ffi(ErrorKind::Internal));
146        }
147    }
148
149    Ok(match out_cbor_slot {
150        None => bytes,
151        Some(cbor_boxed_slice) => cbor_boxed_slice.to::<Box<[u8]>>().into(),
152    })
153}