Skip to main content

dpp/data_contract/serialized_version/
mod.rs

1use super::EMPTY_KEYWORDS;
2use crate::data_contract::accessors::v0::DataContractV0Getters;
3use crate::data_contract::associated_token::token_configuration::TokenConfiguration;
4use crate::data_contract::config::moderation::document_schema_grants_moderator_abilities;
5use crate::data_contract::config::DataContractConfig;
6use crate::data_contract::group::Group;
7use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
8use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
9use crate::data_contract::v0::DataContractV0;
10use crate::data_contract::v1::DataContractV1;
11use crate::data_contract::{
12    DataContract, DefinitionName, DocumentName, GroupContractPosition, TokenContractPosition,
13    EMPTY_GROUPS, EMPTY_TOKENS,
14};
15#[cfg(feature = "json-conversion")]
16use crate::serialization::JsonConvertible;
17#[cfg(feature = "value-conversion")]
18use crate::serialization::ValueConvertible;
19use crate::validation::operations::ProtocolValidationOperation;
20use crate::version::PlatformVersion;
21use crate::ProtocolError;
22use bincode::{Decode, DecodeUntrusted, Encode};
23use derive_more::From;
24use platform_value::{Identifier, Value};
25use platform_version::{IntoPlatformVersioned, TryFromPlatformVersioned};
26use platform_versioning::PlatformVersioned;
27#[cfg(feature = "serde-conversion")]
28use serde::{Deserialize, Serialize};
29use std::collections::BTreeMap;
30use std::fmt;
31
32pub(in crate::data_contract) mod v0;
33pub(in crate::data_contract) mod v1;
34
35pub mod property_names {
36    pub const ID: &str = "id";
37    pub const OWNER_ID: &str = "ownerId";
38    pub const VERSION: &str = "version";
39    pub const DEFINITIONS: &str = "$defs";
40}
41
42pub const CONTRACT_DESERIALIZATION_LIMIT: usize = 15000;
43
44/// Represents a field mismatch between two `DataContractInSerializationFormat::V1`
45/// variants, or indicates a format version mismatch.
46///
47/// Used to diagnose why two data contracts are not considered equal
48/// when ignoring auto-generated fields.
49#[derive(Debug, PartialEq, Eq, Clone, Copy)]
50pub enum DataContractMismatch {
51    /// The `id` fields are not equal.
52    Id,
53    /// The `config` fields are not equal.
54    Config,
55    /// The `version` fields are not equal.
56    Version,
57    /// The `owner_id` fields are not equal.
58    OwnerId,
59    /// The `schema_defs` fields are not equal.
60    SchemaDefs,
61    /// The `document_schemas` fields are not equal.
62    DocumentSchemas,
63    /// The `groups` fields are not equal.
64    Groups,
65    /// The `tokens` fields are not equal.
66    Tokens,
67    /// The `keywords` fields are not equal.
68    Keywords,
69    /// The `description` fields are not equal.
70    Description,
71    /// The two variants are of different serialization formats (e.g., V0 vs V1).
72    FormatVersionMismatch,
73    /// The two variants are different in V0.
74    V0Mismatch,
75}
76
77impl fmt::Display for DataContractMismatch {
78    /// Formats the enum into a human-readable string describing the mismatch.
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        let description = match self {
81            DataContractMismatch::Id => "ID fields differ",
82            DataContractMismatch::Config => "Config fields differ",
83            DataContractMismatch::Version => "Version fields differ",
84            DataContractMismatch::OwnerId => "Owner ID fields differ",
85            DataContractMismatch::SchemaDefs => "Schema definitions differ",
86            DataContractMismatch::DocumentSchemas => "Document schemas differ",
87            DataContractMismatch::Groups => "Groups differ",
88            DataContractMismatch::Tokens => "Tokens differ",
89            DataContractMismatch::Keywords => "Keywords differ",
90            DataContractMismatch::Description => "Description fields differ",
91            DataContractMismatch::FormatVersionMismatch => {
92                "Serialization format versions differ (e.g., V0 vs V1)"
93            }
94            DataContractMismatch::V0Mismatch => "V0 versions differ",
95        };
96        write!(f, "{}", description)
97    }
98}
99
100#[cfg_attr(
101    all(feature = "json-conversion", feature = "serde-conversion"),
102    derive(JsonConvertible)
103)]
104#[cfg_attr(
105    all(feature = "value-conversion", feature = "serde-conversion"),
106    derive(ValueConvertible)
107)]
108#[derive(Debug, Clone, Encode, Decode, PartialEq, PlatformVersioned, From, DecodeUntrusted)]
109#[cfg_attr(
110    feature = "serde-conversion",
111    derive(Serialize, Deserialize),
112    serde(tag = "$formatVersion")
113)]
114pub enum DataContractInSerializationFormat {
115    #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
116    V0(DataContractInSerializationFormatV0),
117    #[cfg_attr(feature = "serde-conversion", serde(rename = "1"))]
118    V1(DataContractInSerializationFormatV1),
119}
120
121impl DataContractInSerializationFormat {
122    /// Returns the unique identifier for the data contract.
123    pub fn id(&self) -> Identifier {
124        match self {
125            DataContractInSerializationFormat::V0(v0) => v0.id,
126            DataContractInSerializationFormat::V1(v1) => v1.id,
127        }
128    }
129
130    /// Returns the owner identifier for the data contract.
131    pub fn owner_id(&self) -> Identifier {
132        match self {
133            DataContractInSerializationFormat::V0(v0) => v0.owner_id,
134            DataContractInSerializationFormat::V1(v1) => v1.owner_id,
135        }
136    }
137
138    pub fn document_schemas(&self) -> &BTreeMap<DocumentName, Value> {
139        match self {
140            DataContractInSerializationFormat::V0(v0) => &v0.document_schemas,
141            DataContractInSerializationFormat::V1(v1) => &v1.document_schemas,
142        }
143    }
144
145    /// Whether any document schema gives the contract's moderators an ability
146    /// (`moderatorAbilities` deleting documents or changing their fields), read from the raw
147    /// schemas: a contract in this format has no parsed document types. Used where a
148    /// moderation declaration is validated before the contract is parsed, since a
149    /// declaration that keeps no list is only valid with such a document type.
150    pub fn has_document_type_with_moderator_abilities(&self) -> bool {
151        self.document_schemas()
152            .values()
153            .any(document_schema_grants_moderator_abilities)
154    }
155
156    pub fn document_schemas_mut(&mut self) -> &mut BTreeMap<DocumentName, Value> {
157        match self {
158            DataContractInSerializationFormat::V0(v0) => &mut v0.document_schemas,
159            DataContractInSerializationFormat::V1(v1) => &mut v1.document_schemas,
160        }
161    }
162
163    pub fn schema_defs(&self) -> Option<&BTreeMap<DefinitionName, Value>> {
164        match self {
165            DataContractInSerializationFormat::V0(v0) => v0.schema_defs.as_ref(),
166            DataContractInSerializationFormat::V1(v1) => v1.schema_defs.as_ref(),
167        }
168    }
169
170    pub fn version(&self) -> u32 {
171        match self {
172            DataContractInSerializationFormat::V0(v0) => v0.version,
173            DataContractInSerializationFormat::V1(v1) => v1.version,
174        }
175    }
176
177    /// Returns the config for the data contract.
178    pub fn config(&self) -> &DataContractConfig {
179        match self {
180            DataContractInSerializationFormat::V0(v0) => &v0.config,
181            DataContractInSerializationFormat::V1(v1) => &v1.config,
182        }
183    }
184
185    pub fn groups(&self) -> &BTreeMap<GroupContractPosition, Group> {
186        match self {
187            DataContractInSerializationFormat::V0(_) => &EMPTY_GROUPS,
188            DataContractInSerializationFormat::V1(v1) => &v1.groups,
189        }
190    }
191    pub fn tokens(&self) -> &BTreeMap<TokenContractPosition, TokenConfiguration> {
192        match self {
193            DataContractInSerializationFormat::V0(_) => &EMPTY_TOKENS,
194            DataContractInSerializationFormat::V1(v1) => &v1.tokens,
195        }
196    }
197
198    pub fn keywords(&self) -> &Vec<String> {
199        match self {
200            DataContractInSerializationFormat::V0(_) => &EMPTY_KEYWORDS,
201            DataContractInSerializationFormat::V1(v1) => &v1.keywords,
202        }
203    }
204
205    pub fn description(&self) -> &Option<String> {
206        match self {
207            DataContractInSerializationFormat::V0(_) => &None,
208            DataContractInSerializationFormat::V1(v1) => &v1.description,
209        }
210    }
211
212    /// Compares `self` to another `DataContractInSerializationFormat` instance
213    /// and returns the first mismatching field, if any.
214    ///
215    /// This comparison ignores auto-generated fields and is only sensitive to
216    /// significant differences in contract content. For V0 formats, any difference
217    /// results in a generic mismatch. For differing format versions (V0 vs V1),
218    /// a `FormatVersionMismatch` is returned.
219    ///
220    /// # Returns
221    ///
222    /// - `None` if the contracts are equal according to the relevant fields.
223    /// - `Some(DataContractMismatch)` indicating the first field where they differ.
224    pub fn first_mismatch(&self, other: &Self) -> Option<DataContractMismatch> {
225        match (self, other) {
226            (
227                DataContractInSerializationFormat::V0(v0_self),
228                DataContractInSerializationFormat::V0(v0_other),
229            ) => {
230                if v0_self != v0_other {
231                    Some(DataContractMismatch::V0Mismatch)
232                } else {
233                    None
234                }
235            }
236            (
237                DataContractInSerializationFormat::V1(v1_self),
238                DataContractInSerializationFormat::V1(v1_other),
239            ) => {
240                if v1_self.id != v1_other.id {
241                    Some(DataContractMismatch::Id)
242                } else if v1_self.config != v1_other.config {
243                    Some(DataContractMismatch::Config)
244                } else if v1_self.version != v1_other.version {
245                    Some(DataContractMismatch::Version)
246                } else if v1_self.owner_id != v1_other.owner_id {
247                    Some(DataContractMismatch::OwnerId)
248                } else if v1_self.schema_defs != v1_other.schema_defs {
249                    Some(DataContractMismatch::SchemaDefs)
250                } else if v1_self.document_schemas != v1_other.document_schemas {
251                    Some(DataContractMismatch::DocumentSchemas)
252                } else if v1_self.groups != v1_other.groups {
253                    Some(DataContractMismatch::Groups)
254                } else if v1_self.tokens != v1_other.tokens {
255                    Some(DataContractMismatch::Tokens)
256                } else if v1_self.keywords.len() != v1_other.keywords.len()
257                    || v1_self
258                        .keywords
259                        .iter()
260                        .zip(v1_other.keywords.iter())
261                        .any(|(a, b)| a.to_lowercase() != b.to_lowercase())
262                {
263                    Some(DataContractMismatch::Keywords)
264                } else if v1_self.description != v1_other.description {
265                    Some(DataContractMismatch::Description)
266                } else {
267                    None
268                }
269            }
270            _ => Some(DataContractMismatch::FormatVersionMismatch),
271        }
272    }
273}
274
275impl TryFromPlatformVersioned<DataContractV0> for DataContractInSerializationFormat {
276    type Error = ProtocolError;
277
278    fn try_from_platform_versioned(
279        value: DataContractV0,
280        platform_version: &PlatformVersion,
281    ) -> Result<Self, Self::Error> {
282        value
283            .config
284            .ensure_admitted_by_platform_version(platform_version)?;
285        match platform_version
286            .dpp
287            .contract_versions
288            .contract_serialization_version
289            .default_current_version
290        {
291            0 => {
292                let v0_format: DataContractInSerializationFormatV0 =
293                    DataContract::V0(value).into_platform_versioned(platform_version);
294                Ok(v0_format.into())
295            }
296            1 => {
297                let v1_format: DataContractInSerializationFormatV1 =
298                    DataContract::V0(value).into_platform_versioned(platform_version);
299                Ok(v1_format.into())
300            }
301            version => Err(ProtocolError::UnknownVersionMismatch {
302                method: "DataContract::serialize_to_default_current_version".to_string(),
303                known_versions: vec![0, 1],
304                received: version,
305            }),
306        }
307    }
308}
309
310impl TryFromPlatformVersioned<&DataContractV0> for DataContractInSerializationFormat {
311    type Error = ProtocolError;
312
313    fn try_from_platform_versioned(
314        value: &DataContractV0,
315        platform_version: &PlatformVersion,
316    ) -> Result<Self, Self::Error> {
317        value
318            .config
319            .ensure_admitted_by_platform_version(platform_version)?;
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::V0(value.to_owned()).into_platform_versioned(platform_version);
329                Ok(v0_format.into())
330            }
331            1 => {
332                let v1_format: DataContractInSerializationFormatV1 =
333                    DataContract::V0(value.to_owned()).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        value
353            .config
354            .ensure_admitted_by_platform_version(platform_version)?;
355        match platform_version
356            .dpp
357            .contract_versions
358            .contract_serialization_version
359            .default_current_version
360        {
361            0 => {
362                let v0_format: DataContractInSerializationFormatV0 =
363                    DataContract::V1(value).into_platform_versioned(platform_version);
364                Ok(v0_format.into())
365            }
366            1 => {
367                let v1_format: DataContractInSerializationFormatV1 =
368                    DataContract::V1(value).into_platform_versioned(platform_version);
369                Ok(v1_format.into())
370            }
371            version => Err(ProtocolError::UnknownVersionMismatch {
372                method: "DataContract::serialize_to_default_current_version".to_string(),
373                known_versions: vec![0, 1],
374                received: version,
375            }),
376        }
377    }
378}
379
380impl TryFromPlatformVersioned<&DataContractV1> for DataContractInSerializationFormat {
381    type Error = ProtocolError;
382
383    fn try_from_platform_versioned(
384        value: &DataContractV1,
385        platform_version: &PlatformVersion,
386    ) -> Result<Self, Self::Error> {
387        value
388            .config
389            .ensure_admitted_by_platform_version(platform_version)?;
390        match platform_version
391            .dpp
392            .contract_versions
393            .contract_serialization_version
394            .default_current_version
395        {
396            0 => {
397                let v0_format: DataContractInSerializationFormatV0 =
398                    DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
399                Ok(v0_format.into())
400            }
401            1 => {
402                let v1_format: DataContractInSerializationFormatV1 =
403                    DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
404                Ok(v1_format.into())
405            }
406            version => Err(ProtocolError::UnknownVersionMismatch {
407                method: "DataContract::serialize_to_default_current_version".to_string(),
408                known_versions: vec![0, 1],
409                received: version,
410            }),
411        }
412    }
413}
414
415impl TryFromPlatformVersioned<&DataContract> for DataContractInSerializationFormat {
416    type Error = ProtocolError;
417
418    fn try_from_platform_versioned(
419        value: &DataContract,
420        platform_version: &PlatformVersion,
421    ) -> Result<Self, Self::Error> {
422        value
423            .config()
424            .ensure_admitted_by_platform_version(platform_version)?;
425        match platform_version
426            .dpp
427            .contract_versions
428            .contract_serialization_version
429            .default_current_version
430        {
431            0 => {
432                let v0_format: DataContractInSerializationFormatV0 =
433                    value.clone().into_platform_versioned(platform_version);
434                Ok(v0_format.into())
435            }
436            1 => {
437                let v1_format: DataContractInSerializationFormatV1 =
438                    value.clone().into_platform_versioned(platform_version);
439                Ok(v1_format.into())
440            }
441            version => Err(ProtocolError::UnknownVersionMismatch {
442                method: "DataContract::serialize_to_default_current_version".to_string(),
443                known_versions: vec![0, 1],
444                received: version,
445            }),
446        }
447    }
448}
449
450impl TryFromPlatformVersioned<DataContract> for DataContractInSerializationFormat {
451    type Error = ProtocolError;
452
453    fn try_from_platform_versioned(
454        value: DataContract,
455        platform_version: &PlatformVersion,
456    ) -> Result<Self, Self::Error> {
457        value
458            .config()
459            .ensure_admitted_by_platform_version(platform_version)?;
460        match platform_version
461            .dpp
462            .contract_versions
463            .contract_serialization_version
464            .default_current_version
465        {
466            0 => {
467                let v0_format: DataContractInSerializationFormatV0 =
468                    value.into_platform_versioned(platform_version);
469                Ok(v0_format.into())
470            }
471            1 => {
472                let v1_format: DataContractInSerializationFormatV1 =
473                    value.into_platform_versioned(platform_version);
474                Ok(v1_format.into())
475            }
476            version => Err(ProtocolError::UnknownVersionMismatch {
477                method: "DataContract::serialize_consume_to_default_current_version".to_string(),
478                known_versions: vec![0, 1],
479                received: version,
480            }),
481        }
482    }
483}
484
485impl DataContract {
486    pub fn try_from_platform_versioned(
487        value: DataContractInSerializationFormat,
488        full_validation: bool,
489        validation_operations: &mut Vec<ProtocolValidationOperation>,
490        platform_version: &PlatformVersion,
491    ) -> Result<Self, ProtocolError> {
492        match platform_version
493            .dpp
494            .contract_versions
495            .contract_structure_version
496        {
497            0 => DataContractV0::try_from_platform_versioned(
498                value,
499                full_validation,
500                validation_operations,
501                platform_version,
502            )
503            .map(|contract| contract.into()),
504            1 => DataContractV1::try_from_platform_versioned(
505                value,
506                full_validation,
507                validation_operations,
508                platform_version,
509            )
510            .map(|contract| contract.into()),
511            version => Err(ProtocolError::UnknownVersionMismatch {
512                method: "DataContract::try_from_platform_versioned".to_string(),
513                known_versions: vec![0, 1],
514                received: version,
515            }),
516        }
517    }
518}
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523    use crate::data_contract::accessors::v0::DataContractV0Setters;
524    use crate::data_contract::config::moderation::ContractModerationConfig;
525    use crate::data_contract::config::v0::DataContractConfigV0;
526    use crate::data_contract::config::v1::DataContractConfigV1;
527    use crate::data_contract::group::v0::GroupV0;
528    use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
529    use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
530    use crate::serialization::PlatformSerializableWithPlatformVersion;
531    use crate::state_transition::data_contract_create_transition::DataContractCreateTransition;
532    use crate::tests::fixtures::get_data_contract_fixture;
533    use platform_value::Identifier;
534    use std::collections::BTreeMap;
535
536    /// A contract of the generation `protocol_version` builds, declaring moderation.
537    fn moderated_contract(protocol_version: u32) -> DataContract {
538        let mut contract =
539            get_data_contract_fixture(None, 0, protocol_version).data_contract_owned();
540        let config = contract
541            .config()
542            .clone()
543            .with_moderation(Some(ContractModerationConfig {
544                banlist: true,
545                suspensions: false,
546                moderators: Default::default(),
547                warnings: false,
548            }));
549        contract.set_config(config);
550        contract
551    }
552
553    /// Every way into the serialization format for `contract`.
554    fn every_conversion(
555        contract: &DataContract,
556        platform_version: &PlatformVersion,
557    ) -> Vec<Result<DataContractInSerializationFormat, ProtocolError>> {
558        let mut conversions = vec![
559            DataContractInSerializationFormat::try_from_platform_versioned(
560                contract.clone(),
561                platform_version,
562            ),
563            DataContractInSerializationFormat::try_from_platform_versioned(
564                contract,
565                platform_version,
566            ),
567        ];
568        match contract {
569            DataContract::V0(v0) => {
570                conversions.push(
571                    DataContractInSerializationFormat::try_from_platform_versioned(
572                        v0.clone(),
573                        platform_version,
574                    ),
575                );
576                conversions.push(
577                    DataContractInSerializationFormat::try_from_platform_versioned(
578                        v0,
579                        platform_version,
580                    ),
581                );
582            }
583            DataContract::V1(v1) => {
584                conversions.push(
585                    DataContractInSerializationFormat::try_from_platform_versioned(
586                        v1.clone(),
587                        platform_version,
588                    ),
589                );
590                conversions.push(
591                    DataContractInSerializationFormat::try_from_platform_versioned(
592                        v1,
593                        platform_version,
594                    ),
595                );
596            }
597        }
598        conversions
599    }
600
601    #[test]
602    fn should_refuse_to_serialize_a_moderated_contract_below_protocol_version_14() {
603        let latest = PlatformVersion::latest();
604        let v13 = PlatformVersion::get(13).expect("protocol version 13");
605
606        // Both contract generations, so that all six conversions are reached.
607        assert!(matches!(moderated_contract(1), DataContract::V0(_)));
608        assert!(matches!(moderated_contract(14), DataContract::V1(_)));
609        for contract in [moderated_contract(1), moderated_contract(14)] {
610            // Lowering the config would drop the declaration for good, so nothing serializes
611            // the contract where the platform version cannot carry it.
612            for conversion in every_conversion(&contract, v13) {
613                assert!(matches!(conversion, Err(ProtocolError::NotSupported(_))));
614            }
615            assert!(matches!(
616                contract.serialize_to_bytes_with_platform_version(v13),
617                Err(ProtocolError::NotSupported(_))
618            ));
619            assert!(matches!(
620                DataContractCreateTransition::try_from_platform_versioned(contract.clone(), v13),
621                Err(ProtocolError::NotSupported(_))
622            ));
623
624            for conversion in every_conversion(&contract, latest) {
625                assert!(conversion.is_ok());
626            }
627            assert!(contract
628                .serialize_to_bytes_with_platform_version(latest)
629                .is_ok());
630        }
631    }
632
633    /// Helper to create a default V0 serialization format.
634    fn make_v0() -> DataContractInSerializationFormatV0 {
635        DataContractInSerializationFormatV0 {
636            id: Identifier::default(),
637            config: DataContractConfig::V0(DataContractConfigV0::default()),
638            version: 1,
639            owner_id: Identifier::default(),
640            schema_defs: None,
641            document_schemas: BTreeMap::new(),
642        }
643    }
644
645    /// Helper to create a default V1 serialization format.
646    fn make_v1() -> DataContractInSerializationFormatV1 {
647        DataContractInSerializationFormatV1 {
648            id: Identifier::default(),
649            config: DataContractConfig::V1(DataContractConfigV1::default()),
650            version: 1,
651            owner_id: Identifier::default(),
652            schema_defs: None,
653            document_schemas: BTreeMap::new(),
654            created_at: None,
655            updated_at: None,
656            created_at_block_height: None,
657            updated_at_block_height: None,
658            created_at_epoch: None,
659            updated_at_epoch: None,
660            groups: BTreeMap::new(),
661            tokens: BTreeMap::new(),
662            keywords: vec![],
663            description: None,
664        }
665    }
666
667    // -----------------------------------------------------------------------
668    // first_mismatch: V0-V0
669    // -----------------------------------------------------------------------
670
671    #[test]
672    fn first_mismatch_v0_v0_identical_returns_none() {
673        let a = DataContractInSerializationFormat::V0(make_v0());
674        let b = DataContractInSerializationFormat::V0(make_v0());
675        assert_eq!(a.first_mismatch(&b), None);
676    }
677
678    #[test]
679    fn first_mismatch_v0_v0_different_id() {
680        let mut v0_b = make_v0();
681        v0_b.id = Identifier::from([1u8; 32]);
682        let a = DataContractInSerializationFormat::V0(make_v0());
683        let b = DataContractInSerializationFormat::V0(v0_b);
684        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
685    }
686
687    #[test]
688    fn first_mismatch_v0_v0_different_config() {
689        let mut v0_b = make_v0();
690        let mut cfg = DataContractConfigV0::default();
691        cfg.readonly = !cfg.readonly;
692        v0_b.config = DataContractConfig::V0(cfg);
693        let a = DataContractInSerializationFormat::V0(make_v0());
694        let b = DataContractInSerializationFormat::V0(v0_b);
695        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
696    }
697
698    #[test]
699    fn first_mismatch_v0_v0_different_version() {
700        let mut v0_b = make_v0();
701        v0_b.version = 99;
702        let a = DataContractInSerializationFormat::V0(make_v0());
703        let b = DataContractInSerializationFormat::V0(v0_b);
704        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
705    }
706
707    #[test]
708    fn first_mismatch_v0_v0_different_owner_id() {
709        let mut v0_b = make_v0();
710        v0_b.owner_id = Identifier::from([2u8; 32]);
711        let a = DataContractInSerializationFormat::V0(make_v0());
712        let b = DataContractInSerializationFormat::V0(v0_b);
713        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
714    }
715
716    #[test]
717    fn first_mismatch_v0_v0_different_document_schemas() {
718        let mut v0_b = make_v0();
719        v0_b.document_schemas
720            .insert("doc".to_string(), Value::Bool(true));
721        let a = DataContractInSerializationFormat::V0(make_v0());
722        let b = DataContractInSerializationFormat::V0(v0_b);
723        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
724    }
725
726    // -----------------------------------------------------------------------
727    // first_mismatch: format mismatch (V0 vs V1)
728    // -----------------------------------------------------------------------
729
730    #[test]
731    fn first_mismatch_v0_v1_returns_format_version_mismatch() {
732        let a = DataContractInSerializationFormat::V0(make_v0());
733        let b = DataContractInSerializationFormat::V1(make_v1());
734        assert_eq!(
735            a.first_mismatch(&b),
736            Some(DataContractMismatch::FormatVersionMismatch)
737        );
738    }
739
740    #[test]
741    fn first_mismatch_v1_v0_returns_format_version_mismatch() {
742        let a = DataContractInSerializationFormat::V1(make_v1());
743        let b = DataContractInSerializationFormat::V0(make_v0());
744        assert_eq!(
745            a.first_mismatch(&b),
746            Some(DataContractMismatch::FormatVersionMismatch)
747        );
748    }
749
750    // -----------------------------------------------------------------------
751    // first_mismatch: V1-V1 identical
752    // -----------------------------------------------------------------------
753
754    #[test]
755    fn first_mismatch_v1_v1_identical_returns_none() {
756        let a = DataContractInSerializationFormat::V1(make_v1());
757        let b = DataContractInSerializationFormat::V1(make_v1());
758        assert_eq!(a.first_mismatch(&b), None);
759    }
760
761    // -----------------------------------------------------------------------
762    // first_mismatch: V1-V1 field-by-field mismatches
763    // -----------------------------------------------------------------------
764
765    #[test]
766    fn first_mismatch_v1_v1_different_id() {
767        let mut v1_b = make_v1();
768        v1_b.id = Identifier::from([1u8; 32]);
769        let a = DataContractInSerializationFormat::V1(make_v1());
770        let b = DataContractInSerializationFormat::V1(v1_b);
771        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
772    }
773
774    #[test]
775    fn first_mismatch_v1_v1_different_config() {
776        let mut v1_b = make_v1();
777        let mut cfg = DataContractConfigV1::default();
778        cfg.readonly = !cfg.readonly;
779        v1_b.config = DataContractConfig::V1(cfg);
780        let a = DataContractInSerializationFormat::V1(make_v1());
781        let b = DataContractInSerializationFormat::V1(v1_b);
782        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Config));
783    }
784
785    #[test]
786    fn first_mismatch_v1_v1_different_version() {
787        let mut v1_b = make_v1();
788        v1_b.version = 42;
789        let a = DataContractInSerializationFormat::V1(make_v1());
790        let b = DataContractInSerializationFormat::V1(v1_b);
791        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Version));
792    }
793
794    #[test]
795    fn first_mismatch_v1_v1_different_owner_id() {
796        let mut v1_b = make_v1();
797        v1_b.owner_id = Identifier::from([3u8; 32]);
798        let a = DataContractInSerializationFormat::V1(make_v1());
799        let b = DataContractInSerializationFormat::V1(v1_b);
800        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::OwnerId));
801    }
802
803    #[test]
804    fn first_mismatch_v1_v1_different_schema_defs() {
805        let mut v1_b = make_v1();
806        let mut defs = BTreeMap::new();
807        defs.insert("someDef".to_string(), Value::Bool(true));
808        v1_b.schema_defs = Some(defs);
809        let a = DataContractInSerializationFormat::V1(make_v1());
810        let b = DataContractInSerializationFormat::V1(v1_b);
811        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::SchemaDefs));
812    }
813
814    #[test]
815    fn first_mismatch_v1_v1_different_document_schemas() {
816        let mut v1_b = make_v1();
817        v1_b.document_schemas
818            .insert("doc".to_string(), Value::U64(1));
819        let a = DataContractInSerializationFormat::V1(make_v1());
820        let b = DataContractInSerializationFormat::V1(v1_b);
821        assert_eq!(
822            a.first_mismatch(&b),
823            Some(DataContractMismatch::DocumentSchemas)
824        );
825    }
826
827    #[test]
828    fn first_mismatch_v1_v1_different_groups() {
829        let mut v1_b = make_v1();
830        v1_b.groups.insert(
831            0,
832            Group::V0(GroupV0 {
833                members: Default::default(),
834                required_power: 1,
835            }),
836        );
837        let a = DataContractInSerializationFormat::V1(make_v1());
838        let b = DataContractInSerializationFormat::V1(v1_b);
839        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Groups));
840    }
841
842    #[test]
843    fn first_mismatch_v1_v1_different_tokens() {
844        let mut v1_b = make_v1();
845        v1_b.tokens.insert(
846            0,
847            TokenConfiguration::V0(
848                crate::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0::default_most_restrictive(),
849            ),
850        );
851        let a = DataContractInSerializationFormat::V1(make_v1());
852        let b = DataContractInSerializationFormat::V1(v1_b);
853        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Tokens));
854    }
855
856    #[test]
857    fn first_mismatch_v1_v1_different_keywords() {
858        let mut v1_b = make_v1();
859        v1_b.keywords = vec!["test".to_string()];
860        let a = DataContractInSerializationFormat::V1(make_v1());
861        let b = DataContractInSerializationFormat::V1(v1_b);
862        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
863    }
864
865    #[test]
866    fn first_mismatch_v1_v1_keywords_case_insensitive_match() {
867        let mut v1_a = make_v1();
868        v1_a.keywords = vec!["Test".to_string()];
869        let mut v1_b = make_v1();
870        v1_b.keywords = vec!["test".to_string()];
871        let a = DataContractInSerializationFormat::V1(v1_a);
872        let b = DataContractInSerializationFormat::V1(v1_b);
873        // The comparison uses to_lowercase, so "Test" and "test" should match
874        assert_eq!(a.first_mismatch(&b), None);
875    }
876
877    #[test]
878    fn first_mismatch_v1_v1_keywords_different_length() {
879        let mut v1_a = make_v1();
880        v1_a.keywords = vec!["a".to_string()];
881        let mut v1_b = make_v1();
882        v1_b.keywords = vec!["a".to_string(), "b".to_string()];
883        let a = DataContractInSerializationFormat::V1(v1_a);
884        let b = DataContractInSerializationFormat::V1(v1_b);
885        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
886    }
887
888    #[test]
889    fn first_mismatch_v1_v1_different_description() {
890        let mut v1_b = make_v1();
891        v1_b.description = Some("a description".to_string());
892        let a = DataContractInSerializationFormat::V1(make_v1());
893        let b = DataContractInSerializationFormat::V1(v1_b);
894        assert_eq!(
895            a.first_mismatch(&b),
896            Some(DataContractMismatch::Description)
897        );
898    }
899
900    // -----------------------------------------------------------------------
901    // first_mismatch: priority ordering in V1 (id detected before config, etc.)
902    // -----------------------------------------------------------------------
903
904    #[test]
905    fn first_mismatch_v1_v1_id_takes_priority_over_config() {
906        let mut v1_b = make_v1();
907        v1_b.id = Identifier::from([5u8; 32]);
908        let mut cfg = DataContractConfigV1::default();
909        cfg.readonly = !cfg.readonly;
910        v1_b.config = DataContractConfig::V1(cfg);
911        let a = DataContractInSerializationFormat::V1(make_v1());
912        let b = DataContractInSerializationFormat::V1(v1_b);
913        // Id is checked before config
914        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
915    }
916
917    // -----------------------------------------------------------------------
918    // DataContractMismatch Display
919    // -----------------------------------------------------------------------
920
921    #[test]
922    fn data_contract_mismatch_display() {
923        assert_eq!(format!("{}", DataContractMismatch::Id), "ID fields differ");
924        assert_eq!(
925            format!("{}", DataContractMismatch::FormatVersionMismatch),
926            "Serialization format versions differ (e.g., V0 vs V1)"
927        );
928        assert_eq!(
929            format!("{}", DataContractMismatch::V0Mismatch),
930            "V0 versions differ"
931        );
932        assert_eq!(format!("{}", DataContractMismatch::Tokens), "Tokens differ");
933        assert_eq!(
934            format!("{}", DataContractMismatch::Keywords),
935            "Keywords differ"
936        );
937        assert_eq!(
938            format!("{}", DataContractMismatch::Description),
939            "Description fields differ"
940        );
941    }
942
943    // -----------------------------------------------------------------------
944    // Accessor methods
945    // -----------------------------------------------------------------------
946
947    #[test]
948    fn accessor_id_v0() {
949        let v0 = make_v0();
950        let expected_id = v0.id;
951        let format = DataContractInSerializationFormat::V0(v0);
952        assert_eq!(format.id(), expected_id);
953    }
954
955    #[test]
956    fn accessor_id_v1() {
957        let v1 = make_v1();
958        let expected_id = v1.id;
959        let format = DataContractInSerializationFormat::V1(v1);
960        assert_eq!(format.id(), expected_id);
961    }
962
963    #[test]
964    fn accessor_owner_id_v0() {
965        let mut v0 = make_v0();
966        v0.owner_id = Identifier::from([7u8; 32]);
967        let expected = v0.owner_id;
968        let format = DataContractInSerializationFormat::V0(v0);
969        assert_eq!(format.owner_id(), expected);
970    }
971
972    #[test]
973    fn accessor_version_v0() {
974        let mut v0 = make_v0();
975        v0.version = 10;
976        let format = DataContractInSerializationFormat::V0(v0);
977        assert_eq!(format.version(), 10);
978    }
979
980    #[test]
981    fn accessor_version_v1() {
982        let mut v1 = make_v1();
983        v1.version = 20;
984        let format = DataContractInSerializationFormat::V1(v1);
985        assert_eq!(format.version(), 20);
986    }
987
988    #[test]
989    fn accessor_groups_v0_returns_empty() {
990        let format = DataContractInSerializationFormat::V0(make_v0());
991        assert!(format.groups().is_empty());
992    }
993
994    #[test]
995    fn accessor_tokens_v0_returns_empty() {
996        let format = DataContractInSerializationFormat::V0(make_v0());
997        assert!(format.tokens().is_empty());
998    }
999
1000    #[test]
1001    fn accessor_keywords_v0_returns_empty() {
1002        let format = DataContractInSerializationFormat::V0(make_v0());
1003        assert!(format.keywords().is_empty());
1004    }
1005
1006    #[test]
1007    fn accessor_description_v0_returns_none() {
1008        let format = DataContractInSerializationFormat::V0(make_v0());
1009        assert_eq!(format.description(), &None);
1010    }
1011
1012    #[test]
1013    fn accessor_keywords_v1() {
1014        let mut v1 = make_v1();
1015        v1.keywords = vec!["hello".to_string()];
1016        let format = DataContractInSerializationFormat::V1(v1);
1017        assert_eq!(format.keywords(), &vec!["hello".to_string()]);
1018    }
1019
1020    #[test]
1021    fn accessor_description_v1_some() {
1022        let mut v1 = make_v1();
1023        v1.description = Some("desc".to_string());
1024        let format = DataContractInSerializationFormat::V1(v1);
1025        assert_eq!(format.description(), &Some("desc".to_string()));
1026    }
1027
1028    #[test]
1029    fn accessor_document_schemas_v0() {
1030        let mut v0 = make_v0();
1031        v0.document_schemas
1032            .insert("note".to_string(), Value::Bool(true));
1033        let format = DataContractInSerializationFormat::V0(v0);
1034        assert_eq!(format.document_schemas().len(), 1);
1035        assert!(format.document_schemas().contains_key("note"));
1036    }
1037
1038    #[test]
1039    fn accessor_schema_defs_v0_none() {
1040        let format = DataContractInSerializationFormat::V0(make_v0());
1041        assert!(format.schema_defs().is_none());
1042    }
1043
1044    #[test]
1045    fn accessor_schema_defs_v1_some() {
1046        let mut v1 = make_v1();
1047        let mut defs = BTreeMap::new();
1048        defs.insert("def1".to_string(), Value::Null);
1049        v1.schema_defs = Some(defs);
1050        let format = DataContractInSerializationFormat::V1(v1);
1051        assert!(format.schema_defs().is_some());
1052        assert!(format.schema_defs().unwrap().contains_key("def1"));
1053    }
1054
1055    // -----------------------------------------------------------------------
1056    // TryFromPlatformVersioned: DataContractV0 -> DataContractInSerializationFormat
1057    // -----------------------------------------------------------------------
1058
1059    #[test]
1060    fn try_from_platform_versioned_data_contract_v0_version_0() {
1061        let platform_version = PlatformVersion::first();
1062        // V1 contract versions use default_current_version: 0
1063        let v0 = DataContractV0 {
1064            id: Identifier::from([10u8; 32]),
1065            config: DataContractConfig::V0(DataContractConfigV0::default()),
1066            version: 1,
1067            owner_id: Identifier::from([20u8; 32]),
1068            schema_defs: None,
1069            document_types: BTreeMap::new(),
1070            metadata: None,
1071        };
1072        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1073            v0.clone(),
1074            platform_version,
1075        );
1076        assert!(result.is_ok());
1077        let format = result.unwrap();
1078        assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1079        assert_eq!(format.id(), Identifier::from([10u8; 32]));
1080        assert_eq!(format.owner_id(), Identifier::from([20u8; 32]));
1081    }
1082
1083    #[test]
1084    fn try_from_platform_versioned_data_contract_v0_ref_version_0() {
1085        let platform_version = PlatformVersion::first();
1086        let v0 = DataContractV0 {
1087            id: Identifier::from([11u8; 32]),
1088            config: DataContractConfig::V0(DataContractConfigV0::default()),
1089            version: 2,
1090            owner_id: Identifier::from([22u8; 32]),
1091            schema_defs: None,
1092            document_types: BTreeMap::new(),
1093            metadata: None,
1094        };
1095        let result =
1096            DataContractInSerializationFormat::try_from_platform_versioned(&v0, platform_version);
1097        assert!(result.is_ok());
1098        let format = result.unwrap();
1099        assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1100        assert_eq!(format.version(), 2);
1101    }
1102
1103    #[test]
1104    fn try_from_platform_versioned_data_contract_v0_version_1() {
1105        let platform_version = PlatformVersion::latest();
1106        // Latest uses default_current_version: 1
1107        let v0 = DataContractV0 {
1108            id: Identifier::from([10u8; 32]),
1109            config: DataContractConfig::V0(DataContractConfigV0::default()),
1110            version: 1,
1111            owner_id: Identifier::from([20u8; 32]),
1112            schema_defs: None,
1113            document_types: BTreeMap::new(),
1114            metadata: None,
1115        };
1116        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1117            v0.clone(),
1118            platform_version,
1119        );
1120        assert!(result.is_ok());
1121        let format = result.unwrap();
1122        assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1123    }
1124
1125    // -----------------------------------------------------------------------
1126    // TryFromPlatformVersioned: DataContractV1 -> DataContractInSerializationFormat
1127    // -----------------------------------------------------------------------
1128
1129    #[test]
1130    fn try_from_platform_versioned_data_contract_v1_version_0() {
1131        let platform_version = PlatformVersion::first();
1132        let v1 = DataContractV1 {
1133            id: Identifier::from([10u8; 32]),
1134            config: DataContractConfig::V0(DataContractConfigV0::default()),
1135            version: 1,
1136            owner_id: Identifier::from([20u8; 32]),
1137            schema_defs: None,
1138            document_types: BTreeMap::new(),
1139            created_at: None,
1140            updated_at: None,
1141            created_at_block_height: None,
1142            updated_at_block_height: None,
1143            created_at_epoch: None,
1144            updated_at_epoch: None,
1145            groups: BTreeMap::new(),
1146            tokens: BTreeMap::new(),
1147            keywords: vec![],
1148            description: None,
1149        };
1150        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1151            v1.clone(),
1152            platform_version,
1153        );
1154        assert!(result.is_ok());
1155        let format = result.unwrap();
1156        assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1157    }
1158
1159    #[test]
1160    fn try_from_platform_versioned_data_contract_v1_version_1() {
1161        let platform_version = PlatformVersion::latest();
1162        let v1 = DataContractV1 {
1163            id: Identifier::from([10u8; 32]),
1164            config: DataContractConfig::V1(DataContractConfigV1::default()),
1165            version: 1,
1166            owner_id: Identifier::from([20u8; 32]),
1167            schema_defs: None,
1168            document_types: BTreeMap::new(),
1169            created_at: None,
1170            updated_at: None,
1171            created_at_block_height: None,
1172            updated_at_block_height: None,
1173            created_at_epoch: None,
1174            updated_at_epoch: None,
1175            groups: BTreeMap::new(),
1176            tokens: BTreeMap::new(),
1177            keywords: vec![],
1178            description: None,
1179        };
1180        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1181            v1.clone(),
1182            platform_version,
1183        );
1184        assert!(result.is_ok());
1185        let format = result.unwrap();
1186        assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1187    }
1188
1189    #[test]
1190    fn try_from_platform_versioned_data_contract_v1_ref_version_1() {
1191        let platform_version = PlatformVersion::latest();
1192        let v1 = DataContractV1 {
1193            id: Identifier::from([10u8; 32]),
1194            config: DataContractConfig::V1(DataContractConfigV1::default()),
1195            version: 3,
1196            owner_id: Identifier::from([20u8; 32]),
1197            schema_defs: None,
1198            document_types: BTreeMap::new(),
1199            created_at: None,
1200            updated_at: None,
1201            created_at_block_height: None,
1202            updated_at_block_height: None,
1203            created_at_epoch: None,
1204            updated_at_epoch: None,
1205            groups: BTreeMap::new(),
1206            tokens: BTreeMap::new(),
1207            keywords: vec![],
1208            description: None,
1209        };
1210        let result =
1211            DataContractInSerializationFormat::try_from_platform_versioned(&v1, platform_version);
1212        assert!(result.is_ok());
1213        let format = result.unwrap();
1214        assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1215        assert_eq!(format.version(), 3);
1216    }
1217
1218    // -----------------------------------------------------------------------
1219    // TryFromPlatformVersioned: DataContract -> DataContractInSerializationFormat
1220    // -----------------------------------------------------------------------
1221
1222    #[test]
1223    fn try_from_platform_versioned_data_contract_ref_version_0() {
1224        let platform_version = PlatformVersion::first();
1225        let contract = DataContract::V0(DataContractV0 {
1226            id: Identifier::from([10u8; 32]),
1227            config: DataContractConfig::V0(DataContractConfigV0::default()),
1228            version: 1,
1229            owner_id: Identifier::from([20u8; 32]),
1230            schema_defs: None,
1231            document_types: BTreeMap::new(),
1232            metadata: None,
1233        });
1234        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1235            &contract,
1236            platform_version,
1237        );
1238        assert!(result.is_ok());
1239        assert!(matches!(
1240            result.unwrap(),
1241            DataContractInSerializationFormat::V0(_)
1242        ));
1243    }
1244
1245    #[test]
1246    fn try_from_platform_versioned_data_contract_owned_version_1() {
1247        let platform_version = PlatformVersion::latest();
1248        let contract = DataContract::V0(DataContractV0 {
1249            id: Identifier::from([10u8; 32]),
1250            config: DataContractConfig::V0(DataContractConfigV0::default()),
1251            version: 1,
1252            owner_id: Identifier::from([20u8; 32]),
1253            schema_defs: None,
1254            document_types: BTreeMap::new(),
1255            metadata: None,
1256        });
1257        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1258            contract,
1259            platform_version,
1260        );
1261        assert!(result.is_ok());
1262        assert!(matches!(
1263            result.unwrap(),
1264            DataContractInSerializationFormat::V1(_)
1265        ));
1266    }
1267
1268    // -----------------------------------------------------------------------
1269    // Verify serialization version routing
1270    // -----------------------------------------------------------------------
1271
1272    #[test]
1273    fn first_platform_version_uses_serialization_version_0() {
1274        let pv = PlatformVersion::first();
1275        assert_eq!(
1276            pv.dpp
1277                .contract_versions
1278                .contract_serialization_version
1279                .default_current_version,
1280            0
1281        );
1282    }
1283
1284    #[test]
1285    fn latest_platform_version_uses_serialization_version_1() {
1286        let pv = PlatformVersion::latest();
1287        assert_eq!(
1288            pv.dpp
1289                .contract_versions
1290                .contract_serialization_version
1291                .default_current_version,
1292            1
1293        );
1294    }
1295
1296    #[test]
1297    fn first_platform_version_uses_contract_structure_0() {
1298        let pv = PlatformVersion::first();
1299        assert_eq!(pv.dpp.contract_versions.contract_structure_version, 0);
1300    }
1301
1302    #[test]
1303    fn latest_platform_version_uses_contract_structure_1() {
1304        let pv = PlatformVersion::latest();
1305        assert_eq!(pv.dpp.contract_versions.contract_structure_version, 1);
1306    }
1307}
1308
1309#[cfg(all(
1310    test,
1311    feature = "json-conversion",
1312    feature = "value-conversion",
1313    feature = "serde-conversion"
1314))]
1315mod json_convertible_tests {
1316    use super::*;
1317    use crate::data_contract::config::v0::DataContractConfigV0;
1318    use crate::data_contract::config::DataContractConfig;
1319    use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
1320    use platform_value::Identifier;
1321    use std::collections::BTreeMap;
1322
1323    fn fixture() -> DataContractInSerializationFormat {
1324        DataContractInSerializationFormat::V0(DataContractInSerializationFormatV0 {
1325            id: Identifier::new([0xa1; 32]),
1326            config: DataContractConfig::V0(DataContractConfigV0::default()),
1327            version: 1,
1328            owner_id: Identifier::new([0xb2; 32]),
1329            schema_defs: None,
1330            document_schemas: BTreeMap::new(),
1331        })
1332    }
1333
1334    #[test]
1335    fn json_round_trip_with_full_wire_shape() {
1336        use crate::serialization::JsonConvertible;
1337        use serde_json::json;
1338        let original = fixture();
1339        let json = original.to_json().expect("to_json");
1340        // Tier 3 envelope-only: `DataContractInSerializationFormat` embeds a
1341        // versioned `DataContractConfig` and arbitrary `document_schemas` /
1342        // `schema_defs` Values. The full inline expansion is verified for the
1343        // `DataContractConfig` in its own module. We still pin the top-level
1344        // envelope keys + their types here so that any silent drop / rename /
1345        // re-keying at this layer would fail the test.
1346        assert_eq!(json["$formatVersion"], "0");
1347        assert_eq!(
1348            json["id"],
1349            json!("Bswb3UyeD1pUTaGiE6WvqwFpJZsQSEY1xhJePCDTHdvp")
1350        );
1351        assert_eq!(
1352            json["ownerId"],
1353            json!("D2ZcUbtpG5sKq7XLeB4YnpNnTGSptKCxTddoNeydzJQq")
1354        );
1355        assert_eq!(json["version"], json!(1));
1356        assert_eq!(json["schemaDefs"], json!(null));
1357        assert_eq!(json["documentSchemas"], json!({}));
1358        assert!(json.get("config").is_some(), "config envelope present");
1359        assert_eq!(json["config"]["$formatVersion"], "0");
1360        let recovered = DataContractInSerializationFormat::from_json(json).expect("from_json");
1361        assert_eq!(original, recovered);
1362    }
1363
1364    #[test]
1365    fn value_round_trip_with_full_wire_shape() {
1366        use crate::serialization::ValueConvertible;
1367        use platform_value::Value;
1368        let original = fixture();
1369        let value = original.to_object().expect("to_object");
1370        // Tier 3 envelope-only: see JSON test above. Keys remain `Identifier` /
1371        // `Map` / typed integers in non-HR mode (no base58 stringification).
1372        let map = match &value {
1373            Value::Map(m) => m,
1374            other => panic!("expected Value::Map, got {:?}", other),
1375        };
1376        let get = |k: &str| -> &Value {
1377            map.iter()
1378                .find(|(key, _)| matches!(key, Value::Text(t) if t == k))
1379                .map(|(_, v)| v)
1380                .unwrap_or_else(|| panic!("missing key {k}"))
1381        };
1382        assert_eq!(get("$formatVersion"), &Value::Text("0".to_string()));
1383        assert_eq!(get("id"), &Value::Identifier([0xa1; 32]));
1384        assert_eq!(get("ownerId"), &Value::Identifier([0xb2; 32]));
1385        assert_eq!(get("version"), &Value::U32(1));
1386        assert_eq!(get("schemaDefs"), &Value::Null);
1387        // documentSchemas: empty Map
1388        assert!(matches!(get("documentSchemas"), Value::Map(m) if m.is_empty()));
1389        // config: nested Map with its own $formatVersion="0"
1390        assert!(matches!(get("config"), Value::Map(_)));
1391        let recovered = DataContractInSerializationFormat::from_object(value).expect("from_object");
1392        assert_eq!(original, recovered);
1393    }
1394
1395    #[test]
1396    fn json_preserves_format_version_tag() {
1397        use crate::serialization::JsonConvertible;
1398        let json = fixture().to_json().expect("to_json");
1399        assert_eq!(json["$formatVersion"], "0");
1400    }
1401}