dpp/document/v0/
cbor_conversion.rs

1use crate::document::property_names;
2
3use crate::identity::TimestampMillis;
4use crate::prelude::{BlockHeight, CoreBlockHeight, Revision};
5
6use crate::ProtocolError;
7
8use crate::document::serialization_traits::{
9    DocumentCborMethodsV0, DocumentPlatformValueMethodsV0,
10};
11use crate::document::v0::DocumentV0;
12use crate::version::PlatformVersion;
13use ciborium::Value as CborValue;
14use integer_encoding::VarIntWriter;
15use platform_value::btreemap_extensions::BTreeValueRemoveFromMapHelper;
16use platform_value::{Identifier, Value};
17use serde::{Deserialize, Serialize};
18use std::collections::BTreeMap;
19use std::convert::{TryFrom, TryInto};
20
21#[cfg(feature = "cbor")]
22#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
23pub struct DocumentForCbor {
24    /// The unique document ID.
25    #[serde(rename = "$id")]
26    pub id: [u8; 32],
27
28    /// The document's properties (data).
29    #[serde(flatten)]
30    pub properties: BTreeMap<String, CborValue>,
31
32    /// The ID of the document's owner.
33    #[serde(rename = "$ownerId")]
34    pub owner_id: [u8; 32],
35
36    /// The document revision.
37    #[serde(rename = "$revision")]
38    pub revision: Option<Revision>,
39
40    #[serde(rename = "$createdAt")]
41    pub created_at: Option<TimestampMillis>,
42    #[serde(rename = "$updatedAt")]
43    pub updated_at: Option<TimestampMillis>,
44    #[serde(rename = "$transferredAt")]
45    pub transferred_at: Option<TimestampMillis>,
46
47    #[serde(rename = "$createdAtBlockHeight")]
48    pub created_at_block_height: Option<BlockHeight>,
49    #[serde(rename = "$updatedAtBlockHeight")]
50    pub updated_at_block_height: Option<BlockHeight>,
51    #[serde(rename = "$transferredAtBlockHeight")]
52    pub transferred_at_block_height: Option<BlockHeight>,
53
54    #[serde(rename = "$createdAtCoreBlockHeight")]
55    pub created_at_core_block_height: Option<CoreBlockHeight>,
56    #[serde(rename = "$updatedAtCoreBlockHeight")]
57    pub updated_at_core_block_height: Option<CoreBlockHeight>,
58    #[serde(rename = "$transferredAtCoreBlockHeight")]
59    pub transferred_at_core_block_height: Option<CoreBlockHeight>,
60
61    #[serde(rename = "$creatorId")]
62    pub creator_id: Option<Identifier>,
63}
64
65#[cfg(feature = "cbor")]
66impl TryFrom<DocumentV0> for DocumentForCbor {
67    type Error = ProtocolError;
68
69    fn try_from(value: DocumentV0) -> Result<Self, Self::Error> {
70        let DocumentV0 {
71            id,
72            properties,
73            owner_id,
74            revision,
75            created_at,
76            updated_at,
77            transferred_at,
78            created_at_block_height,
79            updated_at_block_height,
80            transferred_at_block_height,
81            created_at_core_block_height,
82            updated_at_core_block_height,
83            transferred_at_core_block_height,
84            creator_id,
85        } = value;
86        Ok(DocumentForCbor {
87            id: id.to_buffer(),
88            properties: Value::convert_to_cbor_map(properties)
89                .map_err(ProtocolError::ValueError)?,
90            owner_id: owner_id.to_buffer(),
91            revision,
92            created_at,
93            updated_at,
94            transferred_at,
95            created_at_block_height,
96            updated_at_block_height,
97            transferred_at_block_height,
98            created_at_core_block_height,
99            updated_at_core_block_height,
100            transferred_at_core_block_height,
101            creator_id,
102        })
103    }
104}
105
106impl DocumentV0 {
107    /// Reads a CBOR-serialized document and creates a Document from it.
108    /// If Document and Owner IDs are provided, they are used, otherwise they are created.
109    fn from_map(
110        mut document_map: BTreeMap<String, Value>,
111        document_id: Option<[u8; 32]>,
112        owner_id: Option<[u8; 32]>,
113    ) -> Result<Self, ProtocolError> {
114        let owner_id = match owner_id {
115            None => document_map
116                .remove_hash256_bytes(property_names::OWNER_ID)
117                .map_err(ProtocolError::ValueError)?,
118            Some(owner_id) => owner_id,
119        };
120
121        let id = match document_id {
122            None => document_map
123                .remove_hash256_bytes(property_names::ID)
124                .map_err(ProtocolError::ValueError)?,
125            Some(document_id) => document_id,
126        };
127
128        let revision = document_map.remove_optional_integer(property_names::REVISION)?;
129
130        let created_at = document_map.remove_optional_integer(property_names::CREATED_AT)?;
131        let updated_at = document_map.remove_optional_integer(property_names::UPDATED_AT)?;
132        let transferred_at =
133            document_map.remove_optional_integer(property_names::TRANSFERRED_AT)?;
134        let created_at_block_height =
135            document_map.remove_optional_integer(property_names::CREATED_AT_BLOCK_HEIGHT)?;
136        let updated_at_block_height =
137            document_map.remove_optional_integer(property_names::UPDATED_AT_BLOCK_HEIGHT)?;
138        let transferred_at_block_height =
139            document_map.remove_optional_integer(property_names::TRANSFERRED_AT_BLOCK_HEIGHT)?;
140        let created_at_core_block_height =
141            document_map.remove_optional_integer(property_names::CREATED_AT_CORE_BLOCK_HEIGHT)?;
142        let updated_at_core_block_height =
143            document_map.remove_optional_integer(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT)?;
144        let transferred_at_core_block_height = document_map
145            .remove_optional_integer(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT)?;
146
147        let creator_id = document_map
148            .remove_optional_identifier(property_names::CREATOR_ID)
149            .map_err(ProtocolError::ValueError)?;
150
151        // dev-note: properties is everything other than the id and owner id
152        Ok(DocumentV0 {
153            properties: document_map,
154            owner_id: Identifier::new(owner_id),
155            id: Identifier::new(id),
156            revision,
157            created_at,
158            updated_at,
159            transferred_at,
160            created_at_block_height,
161            updated_at_block_height,
162            transferred_at_block_height,
163            created_at_core_block_height,
164            updated_at_core_block_height,
165            transferred_at_core_block_height,
166            creator_id,
167        })
168    }
169}
170
171impl DocumentCborMethodsV0 for DocumentV0 {
172    /// Reads a CBOR-serialized document and creates a Document from it.
173    /// If Document and Owner IDs are provided, they are used, otherwise they are created.
174    fn from_cbor(
175        document_cbor: &[u8],
176        document_id: Option<[u8; 32]>,
177        owner_id: Option<[u8; 32]>,
178        _platform_version: &PlatformVersion,
179    ) -> Result<Self, ProtocolError> {
180        // first we need to deserialize the document and contract indices
181        // we would need dedicated deserialization functions based on the document type
182        let document_cbor_map: BTreeMap<String, CborValue> =
183            ciborium::de::from_reader(document_cbor).map_err(|_| {
184                ProtocolError::InvalidCBOR(
185                    "unable to decode document for document call".to_string(),
186                )
187            })?;
188        let document_map: BTreeMap<String, Value> =
189            Value::convert_from_cbor_map(document_cbor_map).map_err(ProtocolError::ValueError)?;
190        Self::from_map(document_map, document_id, owner_id)
191    }
192
193    fn to_cbor_value(&self) -> Result<CborValue, ProtocolError> {
194        self.to_object()
195            .map(|v| v.try_into().map_err(ProtocolError::ValueError))?
196    }
197
198    /// Serializes the Document to CBOR.
199    fn to_cbor(&self) -> Result<Vec<u8>, ProtocolError> {
200        let mut buffer: Vec<u8> = Vec::new();
201        buffer.write_varint(0).map_err(|_| {
202            ProtocolError::EncodingError("error writing protocol version".to_string())
203        })?;
204        let cbor_document = DocumentForCbor::try_from(self.clone())?;
205        ciborium::ser::into_writer(&cbor_document, &mut buffer).map_err(|_| {
206            ProtocolError::EncodingError("unable to serialize into cbor".to_string())
207        })?;
208        Ok(buffer)
209    }
210}