dpp/document/
mod.rs

1pub use fields::{property_names, IDENTIFIER_FIELDS};
2
3mod accessors;
4#[cfg(feature = "client")]
5mod document_facade;
6#[cfg(feature = "factories")]
7pub mod document_factory;
8pub mod document_methods;
9mod document_patch;
10pub mod errors;
11#[cfg(feature = "extended-document")]
12pub mod extended_document;
13mod fields;
14pub mod generate_document_id;
15pub mod serialization_traits;
16#[cfg(feature = "factories")]
17pub mod specialized_document_factory;
18pub mod transfer;
19mod v0;
20
21pub use accessors::*;
22pub use v0::*;
23
24#[cfg(feature = "extended-document")]
25pub use extended_document::property_names as extended_document_property_names;
26#[cfg(feature = "extended-document")]
27pub use extended_document::ExtendedDocument;
28#[cfg(feature = "extended-document")]
29pub use extended_document::IDENTIFIER_FIELDS as EXTENDED_DOCUMENT_IDENTIFIER_FIELDS;
30
31/// the initial revision of newly created document
32pub const INITIAL_REVISION: u64 = 1;
33
34use crate::data_contract::document_type::DocumentTypeRef;
35use crate::data_contract::DataContract;
36use crate::document::document_methods::{
37    DocumentGetRawForContractV0, DocumentGetRawForDocumentTypeV0, DocumentHashV0Method,
38    DocumentIsEqualIgnoringTimestampsV0, DocumentMethodsV0,
39};
40use crate::document::errors::DocumentError;
41use crate::version::PlatformVersion;
42use crate::ProtocolError;
43use derive_more::From;
44
45use std::fmt;
46use std::fmt::Formatter;
47
48#[derive(Clone, Debug, PartialEq, From)]
49#[cfg_attr(
50    any(feature = "serde-conversion", feature = "serde-conversion"),
51    derive(serde::Serialize, serde::Deserialize),
52    serde(tag = "$formatVersion")
53)]
54pub enum Document {
55    #[cfg_attr(
56        any(feature = "serde-conversion", feature = "serde-conversion"),
57        serde(rename = "0")
58    )]
59    V0(DocumentV0),
60}
61
62impl fmt::Display for Document {
63    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
64        match self {
65            Document::V0(v0) => {
66                write!(f, "v0 : {} ", v0)?;
67            }
68        }
69        Ok(())
70    }
71}
72
73impl DocumentMethodsV0 for Document {
74    /// Return a value given the path to its key and the document type for a contract.
75    fn get_raw_for_contract(
76        &self,
77        key: &str,
78        document_type_name: &str,
79        contract: &DataContract,
80        owner_id: Option<[u8; 32]>,
81        platform_version: &PlatformVersion,
82    ) -> Result<Option<Vec<u8>>, ProtocolError> {
83        match self {
84            Document::V0(document_v0) => {
85                match platform_version
86                    .dpp
87                    .document_versions
88                    .document_method_versions
89                    .get_raw_for_contract
90                {
91                    0 => document_v0.get_raw_for_contract_v0(
92                        key,
93                        document_type_name,
94                        contract,
95                        owner_id,
96                        platform_version,
97                    ),
98                    version => Err(ProtocolError::UnknownVersionMismatch {
99                        method: "DocumentMethodV0::get_raw_for_contract".to_string(),
100                        known_versions: vec![0],
101                        received: version,
102                    }),
103                }
104            }
105        }
106    }
107
108    /// Return a value given the path to its key for a document type.
109    fn get_raw_for_document_type(
110        &self,
111        key_path: &str,
112        document_type: DocumentTypeRef,
113        owner_id: Option<[u8; 32]>,
114        platform_version: &PlatformVersion,
115    ) -> Result<Option<Vec<u8>>, ProtocolError> {
116        match self {
117            Document::V0(document_v0) => {
118                match platform_version
119                    .dpp
120                    .document_versions
121                    .document_method_versions
122                    .get_raw_for_document_type
123                {
124                    0 => document_v0.get_raw_for_document_type_v0(
125                        key_path,
126                        document_type,
127                        owner_id,
128                        platform_version,
129                    ),
130                    version => Err(ProtocolError::UnknownVersionMismatch {
131                        method: "DocumentMethodV0::get_raw_for_document_type".to_string(),
132                        known_versions: vec![0],
133                        received: version,
134                    }),
135                }
136            }
137        }
138    }
139
140    fn hash(
141        &self,
142        contract: &DataContract,
143        document_type: DocumentTypeRef,
144        platform_version: &PlatformVersion,
145    ) -> Result<Vec<u8>, ProtocolError> {
146        match self {
147            Document::V0(document_v0) => {
148                match platform_version
149                    .dpp
150                    .document_versions
151                    .document_method_versions
152                    .hash
153                {
154                    0 => document_v0.hash_v0(contract, document_type, platform_version),
155                    version => Err(ProtocolError::UnknownVersionMismatch {
156                        method: "DocumentMethodV0::hash".to_string(),
157                        known_versions: vec![0],
158                        received: version,
159                    }),
160                }
161            }
162        }
163    }
164
165    fn increment_revision(&mut self) -> Result<(), ProtocolError> {
166        let Some(revision) = self.revision() else {
167            return Err(ProtocolError::Document(Box::new(
168                DocumentError::DocumentNoRevisionError {
169                    document: Box::new(self.clone()),
170                },
171            )));
172        };
173
174        let new_revision = revision
175            .checked_add(1)
176            .ok_or(ProtocolError::Overflow("overflow when adding 1"))?;
177
178        self.set_revision(Some(new_revision));
179
180        Ok(())
181    }
182
183    fn is_equal_ignoring_time_based_fields(
184        &self,
185        rhs: &Self,
186        also_ignore_fields: Option<Vec<&str>>,
187        platform_version: &PlatformVersion,
188    ) -> Result<bool, ProtocolError> {
189        match (self, rhs) {
190            (Document::V0(document_v0), Document::V0(rhs_v0)) => {
191                match platform_version
192                    .dpp
193                    .document_versions
194                    .document_method_versions
195                    .is_equal_ignoring_timestamps
196                {
197                    0 => Ok(document_v0
198                        .is_equal_ignoring_time_based_fields_v0(rhs_v0, also_ignore_fields)),
199                    version => Err(ProtocolError::UnknownVersionMismatch {
200                        method: "DocumentMethodV0::is_equal_ignoring_time_based_fields".to_string(),
201                        known_versions: vec![0],
202                        received: version,
203                    }),
204                }
205            }
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::data_contract::accessors::v0::DataContractV0Getters;
214    use crate::data_contract::document_type::random_document::CreateRandomDocument;
215    use crate::document::serialization_traits::DocumentPlatformConversionMethodsV0;
216    use crate::tests::json_document::json_document_to_contract;
217
218    use regex::Regex;
219
220    #[test]
221    fn test_document_display() {
222        let platform_version = PlatformVersion::first();
223        let contract = json_document_to_contract(
224            "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
225            false,
226            platform_version,
227        )
228        .expect("expected to get contract");
229
230        let document_type = contract
231            .document_type_for_name("profile")
232            .expect("expected to get profile document type");
233        let document = document_type
234            .random_document(Some(3333), platform_version)
235            .expect("expected to get a random document");
236
237        let document_string = format!("{}", document);
238        let pattern = r"v\d+ : id:45ZNwGcxeMpLpYmiVEKKBKXbZfinrhjZLkau1GWizPFX owner_id:2vq574DjKi7ZD8kJ6dMHxT5wu6ZKD2bW5xKAyKAGW7qZ created_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) updated_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) avatarUrl:string y8RD1DbW18RuyblDX7hx\[...\(670\)\] displayName:string y94Itl6mn1yBE publicMessage:string SvAQrzsslj0ESc15GQBQ\[...\(105\)\] .*";
239        let re = Regex::new(pattern).unwrap();
240        assert!(
241            re.is_match(document_string.as_str()),
242            "pattern: {} does not match {}",
243            pattern,
244            document_string
245        );
246    }
247
248    #[test]
249    fn test_serialization_and_deserialization() {
250        let platform_version = PlatformVersion::latest();
251        let contract = json_document_to_contract(
252            "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json",
253            false,
254            platform_version,
255        )
256        .expect("expected to get contract");
257
258        let document_type = contract
259            .document_type_for_name("domain")
260            .expect("expected to get document type");
261        for _ in 0..20 {
262            let document = document_type
263                .random_document(None, platform_version)
264                .expect("expected a document");
265            let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
266                &document,
267                document_type,
268                &contract,
269                platform_version,
270            )
271            .expect("should serialize");
272            let _deserialized = Document::from_bytes(&serialized, document_type, platform_version)
273                .expect("expected to deserialize domain document");
274        }
275    }
276
277    #[test]
278    fn test_serialize_deserialize_over_different_versions_of_document_type() {
279        let platform_version = PlatformVersion::latest();
280        let contract = json_document_to_contract(
281            "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json",
282            false,
283            platform_version,
284        )
285        .expect("expected to get contract");
286
287        let updated_contract = json_document_to_contract(
288            "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract-update-v2-test.json",
289            false,
290            platform_version,
291        )
292        .expect("expected to get contract");
293
294        let document_type = contract
295            .document_type_for_name("domain")
296            .expect("expected to get document type");
297
298        let updated_document_type = updated_contract
299            .document_type_for_name("domain")
300            .expect("expected to get document type");
301
302        // let's test from a document created in the old version, and we try to deserialize it in the new version
303        for _ in 0..20 {
304            let document = document_type
305                .random_document(None, platform_version)
306                .expect("expected a document");
307            let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
308                &document,
309                document_type,
310                &contract,
311                platform_version,
312            )
313            .expect("should serialize");
314            let _deserialized =
315                Document::from_bytes(&serialized, updated_document_type, platform_version)
316                    .expect("expected to deserialize domain document");
317        }
318
319        // let's test from a document created in the new version, and we try to deserialize it with the old version
320        for _ in 0..20 {
321            let document = updated_document_type
322                .random_document(None, platform_version)
323                .expect("expected a document");
324            let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
325                &document,
326                document_type,
327                &contract,
328                platform_version,
329            )
330            .expect("should serialize");
331            let _deserialized = Document::from_bytes(&serialized, document_type, platform_version)
332                .expect("expected to deserialize domain document");
333        }
334    }
335}