dpp/data_contract/serialized_version/
mod.rs

1use super::EMPTY_KEYWORDS;
2use crate::data_contract::associated_token::token_configuration::TokenConfiguration;
3use crate::data_contract::config::DataContractConfig;
4use crate::data_contract::group::Group;
5use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
6use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
7use crate::data_contract::v0::DataContractV0;
8use crate::data_contract::v1::DataContractV1;
9use crate::data_contract::{
10    DataContract, DefinitionName, DocumentName, GroupContractPosition, TokenContractPosition,
11    EMPTY_GROUPS, EMPTY_TOKENS,
12};
13#[cfg(feature = "json-conversion")]
14use crate::serialization::JsonConvertible;
15use crate::validation::operations::ProtocolValidationOperation;
16use crate::version::PlatformVersion;
17use crate::ProtocolError;
18use bincode::{Decode, Encode};
19use derive_more::From;
20use platform_value::{Identifier, Value};
21use platform_version::{IntoPlatformVersioned, TryFromPlatformVersioned};
22use platform_versioning::PlatformVersioned;
23#[cfg(feature = "serde-conversion")]
24use serde::{Deserialize, Serialize};
25use std::collections::BTreeMap;
26use std::fmt;
27
28pub(in crate::data_contract) mod v0;
29pub(in crate::data_contract) mod v1;
30
31pub mod property_names {
32    pub const ID: &str = "id";
33    pub const OWNER_ID: &str = "ownerId";
34    pub const VERSION: &str = "version";
35    pub const DEFINITIONS: &str = "$defs";
36}
37
38pub const CONTRACT_DESERIALIZATION_LIMIT: usize = 15000;
39
40/// Represents a field mismatch between two `DataContractInSerializationFormat::V1`
41/// variants, or indicates a format version mismatch.
42///
43/// Used to diagnose why two data contracts are not considered equal
44/// when ignoring auto-generated fields.
45#[derive(Debug, PartialEq, Eq, Clone, Copy)]
46pub enum DataContractMismatch {
47    /// The `id` fields are not equal.
48    Id,
49    /// The `config` fields are not equal.
50    Config,
51    /// The `version` fields are not equal.
52    Version,
53    /// The `owner_id` fields are not equal.
54    OwnerId,
55    /// The `schema_defs` fields are not equal.
56    SchemaDefs,
57    /// The `document_schemas` fields are not equal.
58    DocumentSchemas,
59    /// The `groups` fields are not equal.
60    Groups,
61    /// The `tokens` fields are not equal.
62    Tokens,
63    /// The `keywords` fields are not equal.
64    Keywords,
65    /// The `description` fields are not equal.
66    Description,
67    /// The two variants are of different serialization formats (e.g., V0 vs V1).
68    FormatVersionMismatch,
69    /// The two variants are different in V0.
70    V0Mismatch,
71}
72
73impl fmt::Display for DataContractMismatch {
74    /// Formats the enum into a human-readable string describing the mismatch.
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        let description = match self {
77            DataContractMismatch::Id => "ID fields differ",
78            DataContractMismatch::Config => "Config fields differ",
79            DataContractMismatch::Version => "Version fields differ",
80            DataContractMismatch::OwnerId => "Owner ID fields differ",
81            DataContractMismatch::SchemaDefs => "Schema definitions differ",
82            DataContractMismatch::DocumentSchemas => "Document schemas differ",
83            DataContractMismatch::Groups => "Groups differ",
84            DataContractMismatch::Tokens => "Tokens differ",
85            DataContractMismatch::Keywords => "Keywords differ",
86            DataContractMismatch::Description => "Description fields differ",
87            DataContractMismatch::FormatVersionMismatch => {
88                "Serialization format versions differ (e.g., V0 vs V1)"
89            }
90            DataContractMismatch::V0Mismatch => "V0 versions differ",
91        };
92        write!(f, "{}", description)
93    }
94}
95
96#[cfg_attr(
97    all(feature = "json-conversion", feature = "serde-conversion"),
98    derive(JsonConvertible)
99)]
100#[derive(Debug, Clone, Encode, Decode, PartialEq, PlatformVersioned, From)]
101#[cfg_attr(
102    feature = "serde-conversion",
103    derive(Serialize, Deserialize),
104    serde(tag = "$formatVersion")
105)]
106pub enum DataContractInSerializationFormat {
107    #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
108    V0(DataContractInSerializationFormatV0),
109    #[cfg_attr(feature = "serde-conversion", serde(rename = "1"))]
110    V1(DataContractInSerializationFormatV1),
111}
112
113impl DataContractInSerializationFormat {
114    /// Returns the unique identifier for the data contract.
115    pub fn id(&self) -> Identifier {
116        match self {
117            DataContractInSerializationFormat::V0(v0) => v0.id,
118            DataContractInSerializationFormat::V1(v1) => v1.id,
119        }
120    }
121
122    /// Returns the owner identifier for the data contract.
123    pub fn owner_id(&self) -> Identifier {
124        match self {
125            DataContractInSerializationFormat::V0(v0) => v0.owner_id,
126            DataContractInSerializationFormat::V1(v1) => v1.owner_id,
127        }
128    }
129
130    pub fn document_schemas(&self) -> &BTreeMap<DocumentName, Value> {
131        match self {
132            DataContractInSerializationFormat::V0(v0) => &v0.document_schemas,
133            DataContractInSerializationFormat::V1(v1) => &v1.document_schemas,
134        }
135    }
136
137    pub fn schema_defs(&self) -> Option<&BTreeMap<DefinitionName, Value>> {
138        match self {
139            DataContractInSerializationFormat::V0(v0) => v0.schema_defs.as_ref(),
140            DataContractInSerializationFormat::V1(v1) => v1.schema_defs.as_ref(),
141        }
142    }
143
144    pub fn version(&self) -> u32 {
145        match self {
146            DataContractInSerializationFormat::V0(v0) => v0.version,
147            DataContractInSerializationFormat::V1(v1) => v1.version,
148        }
149    }
150
151    /// Returns the config for the data contract.
152    pub fn config(&self) -> &DataContractConfig {
153        match self {
154            DataContractInSerializationFormat::V0(v0) => &v0.config,
155            DataContractInSerializationFormat::V1(v1) => &v1.config,
156        }
157    }
158
159    pub fn groups(&self) -> &BTreeMap<GroupContractPosition, Group> {
160        match self {
161            DataContractInSerializationFormat::V0(_) => &EMPTY_GROUPS,
162            DataContractInSerializationFormat::V1(v1) => &v1.groups,
163        }
164    }
165    pub fn tokens(&self) -> &BTreeMap<TokenContractPosition, TokenConfiguration> {
166        match self {
167            DataContractInSerializationFormat::V0(_) => &EMPTY_TOKENS,
168            DataContractInSerializationFormat::V1(v1) => &v1.tokens,
169        }
170    }
171
172    pub fn keywords(&self) -> &Vec<String> {
173        match self {
174            DataContractInSerializationFormat::V0(_) => &EMPTY_KEYWORDS,
175            DataContractInSerializationFormat::V1(v1) => &v1.keywords,
176        }
177    }
178
179    pub fn description(&self) -> &Option<String> {
180        match self {
181            DataContractInSerializationFormat::V0(_) => &None,
182            DataContractInSerializationFormat::V1(v1) => &v1.description,
183        }
184    }
185
186    /// Compares `self` to another `DataContractInSerializationFormat` instance
187    /// and returns the first mismatching field, if any.
188    ///
189    /// This comparison ignores auto-generated fields and is only sensitive to
190    /// significant differences in contract content. For V0 formats, any difference
191    /// results in a generic mismatch. For differing format versions (V0 vs V1),
192    /// a `FormatVersionMismatch` is returned.
193    ///
194    /// # Returns
195    ///
196    /// - `None` if the contracts are equal according to the relevant fields.
197    /// - `Some(DataContractMismatch)` indicating the first field where they differ.
198    pub fn first_mismatch(&self, other: &Self) -> Option<DataContractMismatch> {
199        match (self, other) {
200            (
201                DataContractInSerializationFormat::V0(v0_self),
202                DataContractInSerializationFormat::V0(v0_other),
203            ) => {
204                if v0_self != v0_other {
205                    Some(DataContractMismatch::V0Mismatch)
206                } else {
207                    None
208                }
209            }
210            (
211                DataContractInSerializationFormat::V1(v1_self),
212                DataContractInSerializationFormat::V1(v1_other),
213            ) => {
214                if v1_self.id != v1_other.id {
215                    Some(DataContractMismatch::Id)
216                } else if v1_self.config != v1_other.config {
217                    Some(DataContractMismatch::Config)
218                } else if v1_self.version != v1_other.version {
219                    Some(DataContractMismatch::Version)
220                } else if v1_self.owner_id != v1_other.owner_id {
221                    Some(DataContractMismatch::OwnerId)
222                } else if v1_self.schema_defs != v1_other.schema_defs {
223                    Some(DataContractMismatch::SchemaDefs)
224                } else if v1_self.document_schemas != v1_other.document_schemas {
225                    Some(DataContractMismatch::DocumentSchemas)
226                } else if v1_self.groups != v1_other.groups {
227                    Some(DataContractMismatch::Groups)
228                } else if v1_self.tokens != v1_other.tokens {
229                    Some(DataContractMismatch::Tokens)
230                } else if v1_self.keywords.len() != v1_other.keywords.len()
231                    || v1_self
232                        .keywords
233                        .iter()
234                        .zip(v1_other.keywords.iter())
235                        .any(|(a, b)| a.to_lowercase() != b.to_lowercase())
236                {
237                    Some(DataContractMismatch::Keywords)
238                } else if v1_self.description != v1_other.description {
239                    Some(DataContractMismatch::Description)
240                } else {
241                    None
242                }
243            }
244            _ => Some(DataContractMismatch::FormatVersionMismatch),
245        }
246    }
247}
248
249impl TryFromPlatformVersioned<DataContractV0> for DataContractInSerializationFormat {
250    type Error = ProtocolError;
251
252    fn try_from_platform_versioned(
253        value: DataContractV0,
254        platform_version: &PlatformVersion,
255    ) -> Result<Self, Self::Error> {
256        match platform_version
257            .dpp
258            .contract_versions
259            .contract_serialization_version
260            .default_current_version
261        {
262            0 => {
263                let v0_format: DataContractInSerializationFormatV0 =
264                    DataContract::V0(value).into_platform_versioned(platform_version);
265                Ok(v0_format.into())
266            }
267            1 => {
268                let v1_format: DataContractInSerializationFormatV1 =
269                    DataContract::V0(value).into_platform_versioned(platform_version);
270                Ok(v1_format.into())
271            }
272            version => Err(ProtocolError::UnknownVersionMismatch {
273                method: "DataContract::serialize_to_default_current_version".to_string(),
274                known_versions: vec![0, 1],
275                received: version,
276            }),
277        }
278    }
279}
280
281impl TryFromPlatformVersioned<&DataContractV0> for DataContractInSerializationFormat {
282    type Error = ProtocolError;
283
284    fn try_from_platform_versioned(
285        value: &DataContractV0,
286        platform_version: &PlatformVersion,
287    ) -> Result<Self, Self::Error> {
288        match platform_version
289            .dpp
290            .contract_versions
291            .contract_serialization_version
292            .default_current_version
293        {
294            0 => {
295                let v0_format: DataContractInSerializationFormatV0 =
296                    DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
297                Ok(v0_format.into())
298            }
299            1 => {
300                let v1_format: DataContractInSerializationFormatV1 =
301                    DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
302                Ok(v1_format.into())
303            }
304            version => Err(ProtocolError::UnknownVersionMismatch {
305                method: "DataContract::serialize_to_default_current_version".to_string(),
306                known_versions: vec![0, 1],
307                received: version,
308            }),
309        }
310    }
311}
312
313impl TryFromPlatformVersioned<DataContractV1> for DataContractInSerializationFormat {
314    type Error = ProtocolError;
315
316    fn try_from_platform_versioned(
317        value: DataContractV1,
318        platform_version: &PlatformVersion,
319    ) -> Result<Self, Self::Error> {
320        match platform_version
321            .dpp
322            .contract_versions
323            .contract_serialization_version
324            .default_current_version
325        {
326            0 => {
327                let v0_format: DataContractInSerializationFormatV0 =
328                    DataContract::V1(value).into_platform_versioned(platform_version);
329                Ok(v0_format.into())
330            }
331            1 => {
332                let v1_format: DataContractInSerializationFormatV1 =
333                    DataContract::V1(value).into_platform_versioned(platform_version);
334                Ok(v1_format.into())
335            }
336            version => Err(ProtocolError::UnknownVersionMismatch {
337                method: "DataContract::serialize_to_default_current_version".to_string(),
338                known_versions: vec![0, 1],
339                received: version,
340            }),
341        }
342    }
343}
344
345impl TryFromPlatformVersioned<&DataContractV1> for DataContractInSerializationFormat {
346    type Error = ProtocolError;
347
348    fn try_from_platform_versioned(
349        value: &DataContractV1,
350        platform_version: &PlatformVersion,
351    ) -> Result<Self, Self::Error> {
352        match platform_version
353            .dpp
354            .contract_versions
355            .contract_serialization_version
356            .default_current_version
357        {
358            0 => {
359                let v0_format: DataContractInSerializationFormatV0 =
360                    DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
361                Ok(v0_format.into())
362            }
363            1 => {
364                let v1_format: DataContractInSerializationFormatV1 =
365                    DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
366                Ok(v1_format.into())
367            }
368            version => Err(ProtocolError::UnknownVersionMismatch {
369                method: "DataContract::serialize_to_default_current_version".to_string(),
370                known_versions: vec![0, 1],
371                received: version,
372            }),
373        }
374    }
375}
376
377impl TryFromPlatformVersioned<&DataContract> for DataContractInSerializationFormat {
378    type Error = ProtocolError;
379
380    fn try_from_platform_versioned(
381        value: &DataContract,
382        platform_version: &PlatformVersion,
383    ) -> Result<Self, Self::Error> {
384        match platform_version
385            .dpp
386            .contract_versions
387            .contract_serialization_version
388            .default_current_version
389        {
390            0 => {
391                let v0_format: DataContractInSerializationFormatV0 =
392                    value.clone().into_platform_versioned(platform_version);
393                Ok(v0_format.into())
394            }
395            1 => {
396                let v1_format: DataContractInSerializationFormatV1 =
397                    value.clone().into_platform_versioned(platform_version);
398                Ok(v1_format.into())
399            }
400            version => Err(ProtocolError::UnknownVersionMismatch {
401                method: "DataContract::serialize_to_default_current_version".to_string(),
402                known_versions: vec![0, 1],
403                received: version,
404            }),
405        }
406    }
407}
408
409impl TryFromPlatformVersioned<DataContract> for DataContractInSerializationFormat {
410    type Error = ProtocolError;
411
412    fn try_from_platform_versioned(
413        value: DataContract,
414        platform_version: &PlatformVersion,
415    ) -> Result<Self, Self::Error> {
416        match platform_version
417            .dpp
418            .contract_versions
419            .contract_serialization_version
420            .default_current_version
421        {
422            0 => {
423                let v0_format: DataContractInSerializationFormatV0 =
424                    value.into_platform_versioned(platform_version);
425                Ok(v0_format.into())
426            }
427            1 => {
428                let v1_format: DataContractInSerializationFormatV1 =
429                    value.into_platform_versioned(platform_version);
430                Ok(v1_format.into())
431            }
432            version => Err(ProtocolError::UnknownVersionMismatch {
433                method: "DataContract::serialize_consume_to_default_current_version".to_string(),
434                known_versions: vec![0, 1],
435                received: version,
436            }),
437        }
438    }
439}
440
441impl DataContract {
442    pub fn try_from_platform_versioned(
443        value: DataContractInSerializationFormat,
444        full_validation: bool,
445        validation_operations: &mut Vec<ProtocolValidationOperation>,
446        platform_version: &PlatformVersion,
447    ) -> Result<Self, ProtocolError> {
448        match platform_version
449            .dpp
450            .contract_versions
451            .contract_structure_version
452        {
453            0 => DataContractV0::try_from_platform_versioned(
454                value,
455                full_validation,
456                validation_operations,
457                platform_version,
458            )
459            .map(|contract| contract.into()),
460            1 => DataContractV1::try_from_platform_versioned(
461                value,
462                full_validation,
463                validation_operations,
464                platform_version,
465            )
466            .map(|contract| contract.into()),
467            version => Err(ProtocolError::UnknownVersionMismatch {
468                method: "DataContract::try_from_platform_versioned".to_string(),
469                known_versions: vec![0, 1],
470                received: version,
471            }),
472        }
473    }
474}