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// }