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}