dpp/document/document_factory/
mod.rs

1mod v0;
2
3use crate::data_contract::DataContract;
4use std::collections::BTreeMap;
5
6use crate::version::PlatformVersion;
7use crate::ProtocolError;
8use derive_more::From;
9use platform_value::{Bytes32, Identifier, Value};
10
11use crate::data_contract::document_type::DocumentTypeRef;
12use crate::document::Document;
13#[cfg(feature = "extended-document")]
14use crate::document::ExtendedDocument;
15#[cfg(feature = "state-transitions")]
16use crate::state_transition::batch_transition::{
17    batched_transition::document_transition_action_type::DocumentTransitionActionType,
18    BatchTransition,
19};
20use crate::tokens::token_payment_info::TokenPaymentInfo;
21use crate::util::entropy_generator::EntropyGenerator;
22pub use v0::DocumentFactoryV0;
23
24/// # Document Factory
25///
26/// This module is responsible for creating instances of documents for a specific contract.
27///
28/// ## Versioning
29///
30/// The factory is versioned because the process of creating documents
31/// can change over time. Changes may be due to modifications in
32/// requirements, alterations in the document structure, or evolution in the
33/// dependencies of the document. Versioning allows for these changes to be
34/// tracked and managed effectively, providing flexibility to handle different
35/// versions of documents as needed.
36#[derive(From)]
37pub enum DocumentFactory {
38    /// The version 0 implementation of the data contract factory.
39    V0(DocumentFactoryV0),
40}
41
42impl DocumentFactory {
43    /// Create a new document factory knowing versions
44    pub fn new(protocol_version: u32) -> Result<Self, ProtocolError> {
45        let platform_version = PlatformVersion::get(protocol_version)?;
46        match platform_version
47            .dpp
48            .factory_versions
49            .document_factory_structure_version
50        {
51            0 => Ok(DocumentFactoryV0::new(protocol_version).into()),
52            version => Err(ProtocolError::UnknownVersionMismatch {
53                method: "DocumentFactory::new".to_string(),
54                known_versions: vec![0],
55                received: version,
56            }),
57        }
58    }
59
60    pub fn new_with_entropy_generator(
61        protocol_version: u32,
62        entropy_generator: Box<dyn EntropyGenerator>,
63    ) -> Result<Self, ProtocolError> {
64        let platform_version = PlatformVersion::get(protocol_version)?;
65        match platform_version
66            .dpp
67            .factory_versions
68            .document_factory_structure_version
69        {
70            0 => Ok(DocumentFactoryV0::new_with_entropy_generator(
71                protocol_version,
72                entropy_generator,
73            )
74            .into()),
75            version => Err(ProtocolError::UnknownVersionMismatch {
76                method: "DocumentFactory::new_with_entropy_generator".to_string(),
77                known_versions: vec![0],
78                received: version,
79            }),
80        }
81    }
82
83    pub fn create_document(
84        &self,
85        data_contract: &DataContract,
86        owner_id: Identifier,
87        document_type_name: String,
88        data: Value,
89    ) -> Result<Document, ProtocolError> {
90        match self {
91            DocumentFactory::V0(v0) => v0.create_document_without_time_based_properties(
92                data_contract,
93                owner_id,
94                document_type_name,
95                data,
96            ),
97        }
98    }
99
100    #[cfg(feature = "extended-document")]
101    pub fn create_extended_document(
102        &self,
103        data_contract: &DataContract,
104        owner_id: Identifier,
105        document_type_name: String,
106        data: Value,
107    ) -> Result<ExtendedDocument, ProtocolError> {
108        match self {
109            DocumentFactory::V0(v0) => {
110                v0.create_extended_document(data_contract, owner_id, document_type_name, data)
111            }
112        }
113    }
114
115    #[cfg(feature = "state-transitions")]
116    pub fn create_state_transition<'a>(
117        &self,
118        documents_iter: impl IntoIterator<
119            Item = (
120                DocumentTransitionActionType,
121                Vec<(
122                    Document,
123                    DocumentTypeRef<'a>,
124                    Bytes32,
125                    Option<TokenPaymentInfo>,
126                )>,
127            ),
128        >,
129        nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce
130    ) -> Result<BatchTransition, ProtocolError> {
131        match self {
132            DocumentFactory::V0(v0) => v0.create_state_transition(documents_iter, nonce_counter),
133        }
134    }
135
136    #[cfg(feature = "extended-document")]
137    pub fn create_extended_from_document_buffer(
138        &self,
139        buffer: &[u8],
140        document_type_name: &str,
141        data_contract: &DataContract,
142        platform_version: &PlatformVersion,
143    ) -> Result<ExtendedDocument, ProtocolError> {
144        match self {
145            DocumentFactory::V0(v0) => v0.create_extended_from_document_buffer(
146                buffer,
147                document_type_name,
148                data_contract,
149                platform_version,
150            ),
151        }
152    }
153}
154
155//
156// #[cfg(test)]
157// mod test {
158//     use platform_value::btreemap_extensions::BTreeValueMapHelper;
159//     use platform_value::platform_value;
160//     use platform_value::string_encoding::Encoding;
161//     use std::sync::Arc;
162//
163//     use crate::tests::fixtures::get_extended_documents_fixture;
164//     use crate::{
165//         assert_error_contains,
166//         state_repository::MockStateRepositoryLike,
167//         tests::{
168//             fixtures::{get_data_contract_fixture, get_document_validator_fixture},
169//             utils::generate_random_identifier_struct,
170//         },
171//     };
172//     use crate::document::document_factory::DocumentFactory;
173//
174//     use super::*;
175//
176//     #[test]
177//     fn document_with_type_and_data() {
178//         let mut data_contract = get_data_contract_fixture(None).data_contract;
179//         let document_type = "niceDocument";
180//
181//         let factory = DocumentFactory::new(
182//             1,
183//             get_document_validator_fixture(),
184//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
185//         );
186//         let name = "Cutie";
187//         let contract_id = Identifier::from_string(
188//             "FQco85WbwNgb5ix8QQAH6wurMcgEC5ENSCv5ixG9cj12",
189//             Encoding::Base58,
190//         )
191//             .unwrap();
192//         let owner_id = Identifier::from_string(
193//             "5zcXZpTLWFwZjKjq3ME5KVavtZa9YUaZESVzrndehBhq",
194//             Encoding::Base58,
195//         )
196//             .unwrap();
197//
198//         data_contract.id = contract_id;
199//
200//         let document = factory
201//             .create_extended_document_for_state_transition(
202//                 data_contract,
203//                 owner_id,
204//                 document_type.to_string(),
205//                 platform_value!({ "name": name }),
206//             )
207//             .expect("document creation shouldn't fail");
208//         assert_eq!(document_type, document.document_type_name);
209//         assert_eq!(
210//             name,
211//             document
212//                 .properties()
213//                 .get_str("name")
214//                 .expect("property 'name' should exist")
215//         );
216//         assert_eq!(contract_id, document.data_contract_id);
217//         assert_eq!(owner_id, document.owner_id());
218//         assert_eq!(
219//             document_transition::INITIAL_REVISION,
220//             *document.revision().unwrap()
221//         );
222//         assert!(!document.id().to_string(Encoding::Base58).is_empty());
223//         assert!(document.created_at().is_some());
224//     }
225//
226//     #[test]
227//     fn create_state_transition_no_documents() {
228//         let factory = DocumentFactory::new(
229//             1,
230//             get_document_validator_fixture(),
231//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
232//         );
233//
234//         let result = factory.create_state_transition(vec![]);
235//         assert_error_contains!(result, "No documents were supplied to state transition")
236//     }
237//
238//     #[test]
239//     fn create_transition_mismatch_user_id() {
240//         let data_contract = get_data_contract_fixture(None).data_contract;
241//         let mut documents = get_extended_documents_fixture(data_contract).unwrap();
242//
243//         let factory = DocumentFactory::new(
244//             1,
245//             get_document_validator_fixture(),
246//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
247//         );
248//
249//         documents[0].document.owner_id = generate_random_identifier_struct();
250//
251//         let result = factory.create_state_transition(vec![(DocumentTransitionActionType::Create, documents)]);
252//         assert_error_contains!(result, "Documents have mixed owner ids")
253//     }
254//
255//     #[test]
256//     fn create_transition_invalid_initial_revision() {
257//         let data_contract = get_data_contract_fixture(None).data_contract;
258//         let mut documents = get_extended_documents_fixture(data_contract).unwrap();
259//         documents[0].document.revision = Some(3);
260//
261//         let factory = DocumentFactory::new(
262//             1,
263//             get_document_validator_fixture(),
264//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
265//         );
266//         let result = factory.create_state_transition(vec![(DocumentTransitionActionType::Create, documents)]);
267//         assert_error_contains!(result, "Invalid Document initial revision '3'")
268//     }
269//
270//     #[test]
271//     fn create_transitions_with_passed_documents() {
272//         let data_contract = get_data_contract_fixture(None).data_contract;
273//         let documents = get_extended_documents_fixture(data_contract).unwrap();
274//         let factory = DocumentFactory::new(
275//             1,
276//             get_document_validator_fixture(),
277//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
278//         );
279//
280//         let new_document = documents[0].clone();
281//         let batch_transition = factory
282//             .create_state_transition(vec![
283//                 (DocumentTransitionActionType::Create, documents),
284//                 (DocumentTransitionActionType::Replace, vec![new_document]),
285//             ])
286//             .expect("state transitions should be created");
287//         assert_eq!(11, batch_transition.transitions.len());
288//         assert_eq!(
289//             10,
290//             batch_transition
291//                 .transitions
292//                 .iter()
293//                 .filter(|t| t.as_transition_create().is_some())
294//                 .count()
295//         );
296//         assert_eq!(
297//             1,
298//             batch_transition
299//                 .transitions
300//                 .iter()
301//                 .filter(|t| t.as_transition_replace().is_some())
302//                 .count()
303//         )
304//     }
305// }