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;
15#[cfg(feature = "value-conversion")]
16use crate::serialization::ValueConvertible;
17use crate::validation::operations::ProtocolValidationOperation;
18use crate::version::PlatformVersion;
19use crate::ProtocolError;
20use bincode::{Decode, Encode};
21use derive_more::From;
22use platform_value::{Identifier, Value};
23use platform_version::{IntoPlatformVersioned, TryFromPlatformVersioned};
24use platform_versioning::PlatformVersioned;
25#[cfg(feature = "serde-conversion")]
26use serde::{Deserialize, Serialize};
27use std::collections::BTreeMap;
28use std::fmt;
29
30pub(in crate::data_contract) mod v0;
31pub(in crate::data_contract) mod v1;
32
33pub mod property_names {
34 pub const ID: &str = "id";
35 pub const OWNER_ID: &str = "ownerId";
36 pub const VERSION: &str = "version";
37 pub const DEFINITIONS: &str = "$defs";
38}
39
40pub const CONTRACT_DESERIALIZATION_LIMIT: usize = 15000;
41
42#[derive(Debug, PartialEq, Eq, Clone, Copy)]
48pub enum DataContractMismatch {
49 Id,
51 Config,
53 Version,
55 OwnerId,
57 SchemaDefs,
59 DocumentSchemas,
61 Groups,
63 Tokens,
65 Keywords,
67 Description,
69 FormatVersionMismatch,
71 V0Mismatch,
73}
74
75impl fmt::Display for DataContractMismatch {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 let description = match self {
79 DataContractMismatch::Id => "ID fields differ",
80 DataContractMismatch::Config => "Config fields differ",
81 DataContractMismatch::Version => "Version fields differ",
82 DataContractMismatch::OwnerId => "Owner ID fields differ",
83 DataContractMismatch::SchemaDefs => "Schema definitions differ",
84 DataContractMismatch::DocumentSchemas => "Document schemas differ",
85 DataContractMismatch::Groups => "Groups differ",
86 DataContractMismatch::Tokens => "Tokens differ",
87 DataContractMismatch::Keywords => "Keywords differ",
88 DataContractMismatch::Description => "Description fields differ",
89 DataContractMismatch::FormatVersionMismatch => {
90 "Serialization format versions differ (e.g., V0 vs V1)"
91 }
92 DataContractMismatch::V0Mismatch => "V0 versions differ",
93 };
94 write!(f, "{}", description)
95 }
96}
97
98#[cfg_attr(
99 all(feature = "json-conversion", feature = "serde-conversion"),
100 derive(JsonConvertible)
101)]
102#[cfg_attr(
103 all(feature = "value-conversion", feature = "serde-conversion"),
104 derive(ValueConvertible)
105)]
106#[derive(Debug, Clone, Encode, Decode, PartialEq, PlatformVersioned, From)]
107#[cfg_attr(
108 feature = "serde-conversion",
109 derive(Serialize, Deserialize),
110 serde(tag = "$formatVersion")
111)]
112pub enum DataContractInSerializationFormat {
113 #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
114 V0(DataContractInSerializationFormatV0),
115 #[cfg_attr(feature = "serde-conversion", serde(rename = "1"))]
116 V1(DataContractInSerializationFormatV1),
117}
118
119impl DataContractInSerializationFormat {
120 pub fn id(&self) -> Identifier {
122 match self {
123 DataContractInSerializationFormat::V0(v0) => v0.id,
124 DataContractInSerializationFormat::V1(v1) => v1.id,
125 }
126 }
127
128 pub fn owner_id(&self) -> Identifier {
130 match self {
131 DataContractInSerializationFormat::V0(v0) => v0.owner_id,
132 DataContractInSerializationFormat::V1(v1) => v1.owner_id,
133 }
134 }
135
136 pub fn document_schemas(&self) -> &BTreeMap<DocumentName, Value> {
137 match self {
138 DataContractInSerializationFormat::V0(v0) => &v0.document_schemas,
139 DataContractInSerializationFormat::V1(v1) => &v1.document_schemas,
140 }
141 }
142
143 pub fn document_schemas_mut(&mut self) -> &mut BTreeMap<DocumentName, Value> {
144 match self {
145 DataContractInSerializationFormat::V0(v0) => &mut v0.document_schemas,
146 DataContractInSerializationFormat::V1(v1) => &mut v1.document_schemas,
147 }
148 }
149
150 pub fn schema_defs(&self) -> Option<&BTreeMap<DefinitionName, Value>> {
151 match self {
152 DataContractInSerializationFormat::V0(v0) => v0.schema_defs.as_ref(),
153 DataContractInSerializationFormat::V1(v1) => v1.schema_defs.as_ref(),
154 }
155 }
156
157 pub fn version(&self) -> u32 {
158 match self {
159 DataContractInSerializationFormat::V0(v0) => v0.version,
160 DataContractInSerializationFormat::V1(v1) => v1.version,
161 }
162 }
163
164 pub fn config(&self) -> &DataContractConfig {
166 match self {
167 DataContractInSerializationFormat::V0(v0) => &v0.config,
168 DataContractInSerializationFormat::V1(v1) => &v1.config,
169 }
170 }
171
172 pub fn groups(&self) -> &BTreeMap<GroupContractPosition, Group> {
173 match self {
174 DataContractInSerializationFormat::V0(_) => &EMPTY_GROUPS,
175 DataContractInSerializationFormat::V1(v1) => &v1.groups,
176 }
177 }
178 pub fn tokens(&self) -> &BTreeMap<TokenContractPosition, TokenConfiguration> {
179 match self {
180 DataContractInSerializationFormat::V0(_) => &EMPTY_TOKENS,
181 DataContractInSerializationFormat::V1(v1) => &v1.tokens,
182 }
183 }
184
185 pub fn keywords(&self) -> &Vec<String> {
186 match self {
187 DataContractInSerializationFormat::V0(_) => &EMPTY_KEYWORDS,
188 DataContractInSerializationFormat::V1(v1) => &v1.keywords,
189 }
190 }
191
192 pub fn description(&self) -> &Option<String> {
193 match self {
194 DataContractInSerializationFormat::V0(_) => &None,
195 DataContractInSerializationFormat::V1(v1) => &v1.description,
196 }
197 }
198
199 pub fn first_mismatch(&self, other: &Self) -> Option<DataContractMismatch> {
212 match (self, other) {
213 (
214 DataContractInSerializationFormat::V0(v0_self),
215 DataContractInSerializationFormat::V0(v0_other),
216 ) => {
217 if v0_self != v0_other {
218 Some(DataContractMismatch::V0Mismatch)
219 } else {
220 None
221 }
222 }
223 (
224 DataContractInSerializationFormat::V1(v1_self),
225 DataContractInSerializationFormat::V1(v1_other),
226 ) => {
227 if v1_self.id != v1_other.id {
228 Some(DataContractMismatch::Id)
229 } else if v1_self.config != v1_other.config {
230 Some(DataContractMismatch::Config)
231 } else if v1_self.version != v1_other.version {
232 Some(DataContractMismatch::Version)
233 } else if v1_self.owner_id != v1_other.owner_id {
234 Some(DataContractMismatch::OwnerId)
235 } else if v1_self.schema_defs != v1_other.schema_defs {
236 Some(DataContractMismatch::SchemaDefs)
237 } else if v1_self.document_schemas != v1_other.document_schemas {
238 Some(DataContractMismatch::DocumentSchemas)
239 } else if v1_self.groups != v1_other.groups {
240 Some(DataContractMismatch::Groups)
241 } else if v1_self.tokens != v1_other.tokens {
242 Some(DataContractMismatch::Tokens)
243 } else if v1_self.keywords.len() != v1_other.keywords.len()
244 || v1_self
245 .keywords
246 .iter()
247 .zip(v1_other.keywords.iter())
248 .any(|(a, b)| a.to_lowercase() != b.to_lowercase())
249 {
250 Some(DataContractMismatch::Keywords)
251 } else if v1_self.description != v1_other.description {
252 Some(DataContractMismatch::Description)
253 } else {
254 None
255 }
256 }
257 _ => Some(DataContractMismatch::FormatVersionMismatch),
258 }
259 }
260}
261
262impl TryFromPlatformVersioned<DataContractV0> for DataContractInSerializationFormat {
263 type Error = ProtocolError;
264
265 fn try_from_platform_versioned(
266 value: DataContractV0,
267 platform_version: &PlatformVersion,
268 ) -> Result<Self, Self::Error> {
269 match platform_version
270 .dpp
271 .contract_versions
272 .contract_serialization_version
273 .default_current_version
274 {
275 0 => {
276 let v0_format: DataContractInSerializationFormatV0 =
277 DataContract::V0(value).into_platform_versioned(platform_version);
278 Ok(v0_format.into())
279 }
280 1 => {
281 let v1_format: DataContractInSerializationFormatV1 =
282 DataContract::V0(value).into_platform_versioned(platform_version);
283 Ok(v1_format.into())
284 }
285 version => Err(ProtocolError::UnknownVersionMismatch {
286 method: "DataContract::serialize_to_default_current_version".to_string(),
287 known_versions: vec![0, 1],
288 received: version,
289 }),
290 }
291 }
292}
293
294impl TryFromPlatformVersioned<&DataContractV0> for DataContractInSerializationFormat {
295 type Error = ProtocolError;
296
297 fn try_from_platform_versioned(
298 value: &DataContractV0,
299 platform_version: &PlatformVersion,
300 ) -> Result<Self, Self::Error> {
301 match platform_version
302 .dpp
303 .contract_versions
304 .contract_serialization_version
305 .default_current_version
306 {
307 0 => {
308 let v0_format: DataContractInSerializationFormatV0 =
309 DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
310 Ok(v0_format.into())
311 }
312 1 => {
313 let v1_format: DataContractInSerializationFormatV1 =
314 DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
315 Ok(v1_format.into())
316 }
317 version => Err(ProtocolError::UnknownVersionMismatch {
318 method: "DataContract::serialize_to_default_current_version".to_string(),
319 known_versions: vec![0, 1],
320 received: version,
321 }),
322 }
323 }
324}
325
326impl TryFromPlatformVersioned<DataContractV1> for DataContractInSerializationFormat {
327 type Error = ProtocolError;
328
329 fn try_from_platform_versioned(
330 value: DataContractV1,
331 platform_version: &PlatformVersion,
332 ) -> Result<Self, Self::Error> {
333 match platform_version
334 .dpp
335 .contract_versions
336 .contract_serialization_version
337 .default_current_version
338 {
339 0 => {
340 let v0_format: DataContractInSerializationFormatV0 =
341 DataContract::V1(value).into_platform_versioned(platform_version);
342 Ok(v0_format.into())
343 }
344 1 => {
345 let v1_format: DataContractInSerializationFormatV1 =
346 DataContract::V1(value).into_platform_versioned(platform_version);
347 Ok(v1_format.into())
348 }
349 version => Err(ProtocolError::UnknownVersionMismatch {
350 method: "DataContract::serialize_to_default_current_version".to_string(),
351 known_versions: vec![0, 1],
352 received: version,
353 }),
354 }
355 }
356}
357
358impl TryFromPlatformVersioned<&DataContractV1> for DataContractInSerializationFormat {
359 type Error = ProtocolError;
360
361 fn try_from_platform_versioned(
362 value: &DataContractV1,
363 platform_version: &PlatformVersion,
364 ) -> Result<Self, Self::Error> {
365 match platform_version
366 .dpp
367 .contract_versions
368 .contract_serialization_version
369 .default_current_version
370 {
371 0 => {
372 let v0_format: DataContractInSerializationFormatV0 =
373 DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
374 Ok(v0_format.into())
375 }
376 1 => {
377 let v1_format: DataContractInSerializationFormatV1 =
378 DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
379 Ok(v1_format.into())
380 }
381 version => Err(ProtocolError::UnknownVersionMismatch {
382 method: "DataContract::serialize_to_default_current_version".to_string(),
383 known_versions: vec![0, 1],
384 received: version,
385 }),
386 }
387 }
388}
389
390impl TryFromPlatformVersioned<&DataContract> for DataContractInSerializationFormat {
391 type Error = ProtocolError;
392
393 fn try_from_platform_versioned(
394 value: &DataContract,
395 platform_version: &PlatformVersion,
396 ) -> Result<Self, Self::Error> {
397 match platform_version
398 .dpp
399 .contract_versions
400 .contract_serialization_version
401 .default_current_version
402 {
403 0 => {
404 let v0_format: DataContractInSerializationFormatV0 =
405 value.clone().into_platform_versioned(platform_version);
406 Ok(v0_format.into())
407 }
408 1 => {
409 let v1_format: DataContractInSerializationFormatV1 =
410 value.clone().into_platform_versioned(platform_version);
411 Ok(v1_format.into())
412 }
413 version => Err(ProtocolError::UnknownVersionMismatch {
414 method: "DataContract::serialize_to_default_current_version".to_string(),
415 known_versions: vec![0, 1],
416 received: version,
417 }),
418 }
419 }
420}
421
422impl TryFromPlatformVersioned<DataContract> for DataContractInSerializationFormat {
423 type Error = ProtocolError;
424
425 fn try_from_platform_versioned(
426 value: DataContract,
427 platform_version: &PlatformVersion,
428 ) -> Result<Self, Self::Error> {
429 match platform_version
430 .dpp
431 .contract_versions
432 .contract_serialization_version
433 .default_current_version
434 {
435 0 => {
436 let v0_format: DataContractInSerializationFormatV0 =
437 value.into_platform_versioned(platform_version);
438 Ok(v0_format.into())
439 }
440 1 => {
441 let v1_format: DataContractInSerializationFormatV1 =
442 value.into_platform_versioned(platform_version);
443 Ok(v1_format.into())
444 }
445 version => Err(ProtocolError::UnknownVersionMismatch {
446 method: "DataContract::serialize_consume_to_default_current_version".to_string(),
447 known_versions: vec![0, 1],
448 received: version,
449 }),
450 }
451 }
452}
453
454impl DataContract {
455 pub fn try_from_platform_versioned(
456 value: DataContractInSerializationFormat,
457 full_validation: bool,
458 validation_operations: &mut Vec<ProtocolValidationOperation>,
459 platform_version: &PlatformVersion,
460 ) -> Result<Self, ProtocolError> {
461 match platform_version
462 .dpp
463 .contract_versions
464 .contract_structure_version
465 {
466 0 => DataContractV0::try_from_platform_versioned(
467 value,
468 full_validation,
469 validation_operations,
470 platform_version,
471 )
472 .map(|contract| contract.into()),
473 1 => DataContractV1::try_from_platform_versioned(
474 value,
475 full_validation,
476 validation_operations,
477 platform_version,
478 )
479 .map(|contract| contract.into()),
480 version => Err(ProtocolError::UnknownVersionMismatch {
481 method: "DataContract::try_from_platform_versioned".to_string(),
482 known_versions: vec![0, 1],
483 received: version,
484 }),
485 }
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492 use crate::data_contract::config::v0::DataContractConfigV0;
493 use crate::data_contract::config::v1::DataContractConfigV1;
494 use crate::data_contract::group::v0::GroupV0;
495 use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
496 use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
497 use platform_value::Identifier;
498 use std::collections::BTreeMap;
499
500 fn make_v0() -> DataContractInSerializationFormatV0 {
502 DataContractInSerializationFormatV0 {
503 id: Identifier::default(),
504 config: DataContractConfig::V0(DataContractConfigV0::default()),
505 version: 1,
506 owner_id: Identifier::default(),
507 schema_defs: None,
508 document_schemas: BTreeMap::new(),
509 }
510 }
511
512 fn make_v1() -> DataContractInSerializationFormatV1 {
514 DataContractInSerializationFormatV1 {
515 id: Identifier::default(),
516 config: DataContractConfig::V1(DataContractConfigV1::default()),
517 version: 1,
518 owner_id: Identifier::default(),
519 schema_defs: None,
520 document_schemas: BTreeMap::new(),
521 created_at: None,
522 updated_at: None,
523 created_at_block_height: None,
524 updated_at_block_height: None,
525 created_at_epoch: None,
526 updated_at_epoch: None,
527 groups: BTreeMap::new(),
528 tokens: BTreeMap::new(),
529 keywords: vec![],
530 description: None,
531 }
532 }
533
534 #[test]
539 fn first_mismatch_v0_v0_identical_returns_none() {
540 let a = DataContractInSerializationFormat::V0(make_v0());
541 let b = DataContractInSerializationFormat::V0(make_v0());
542 assert_eq!(a.first_mismatch(&b), None);
543 }
544
545 #[test]
546 fn first_mismatch_v0_v0_different_id() {
547 let mut v0_b = make_v0();
548 v0_b.id = Identifier::from([1u8; 32]);
549 let a = DataContractInSerializationFormat::V0(make_v0());
550 let b = DataContractInSerializationFormat::V0(v0_b);
551 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
552 }
553
554 #[test]
555 fn first_mismatch_v0_v0_different_config() {
556 let mut v0_b = make_v0();
557 let mut cfg = DataContractConfigV0::default();
558 cfg.readonly = !cfg.readonly;
559 v0_b.config = DataContractConfig::V0(cfg);
560 let a = DataContractInSerializationFormat::V0(make_v0());
561 let b = DataContractInSerializationFormat::V0(v0_b);
562 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
563 }
564
565 #[test]
566 fn first_mismatch_v0_v0_different_version() {
567 let mut v0_b = make_v0();
568 v0_b.version = 99;
569 let a = DataContractInSerializationFormat::V0(make_v0());
570 let b = DataContractInSerializationFormat::V0(v0_b);
571 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
572 }
573
574 #[test]
575 fn first_mismatch_v0_v0_different_owner_id() {
576 let mut v0_b = make_v0();
577 v0_b.owner_id = Identifier::from([2u8; 32]);
578 let a = DataContractInSerializationFormat::V0(make_v0());
579 let b = DataContractInSerializationFormat::V0(v0_b);
580 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
581 }
582
583 #[test]
584 fn first_mismatch_v0_v0_different_document_schemas() {
585 let mut v0_b = make_v0();
586 v0_b.document_schemas
587 .insert("doc".to_string(), Value::Bool(true));
588 let a = DataContractInSerializationFormat::V0(make_v0());
589 let b = DataContractInSerializationFormat::V0(v0_b);
590 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
591 }
592
593 #[test]
598 fn first_mismatch_v0_v1_returns_format_version_mismatch() {
599 let a = DataContractInSerializationFormat::V0(make_v0());
600 let b = DataContractInSerializationFormat::V1(make_v1());
601 assert_eq!(
602 a.first_mismatch(&b),
603 Some(DataContractMismatch::FormatVersionMismatch)
604 );
605 }
606
607 #[test]
608 fn first_mismatch_v1_v0_returns_format_version_mismatch() {
609 let a = DataContractInSerializationFormat::V1(make_v1());
610 let b = DataContractInSerializationFormat::V0(make_v0());
611 assert_eq!(
612 a.first_mismatch(&b),
613 Some(DataContractMismatch::FormatVersionMismatch)
614 );
615 }
616
617 #[test]
622 fn first_mismatch_v1_v1_identical_returns_none() {
623 let a = DataContractInSerializationFormat::V1(make_v1());
624 let b = DataContractInSerializationFormat::V1(make_v1());
625 assert_eq!(a.first_mismatch(&b), None);
626 }
627
628 #[test]
633 fn first_mismatch_v1_v1_different_id() {
634 let mut v1_b = make_v1();
635 v1_b.id = Identifier::from([1u8; 32]);
636 let a = DataContractInSerializationFormat::V1(make_v1());
637 let b = DataContractInSerializationFormat::V1(v1_b);
638 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
639 }
640
641 #[test]
642 fn first_mismatch_v1_v1_different_config() {
643 let mut v1_b = make_v1();
644 let mut cfg = DataContractConfigV1::default();
645 cfg.readonly = !cfg.readonly;
646 v1_b.config = DataContractConfig::V1(cfg);
647 let a = DataContractInSerializationFormat::V1(make_v1());
648 let b = DataContractInSerializationFormat::V1(v1_b);
649 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Config));
650 }
651
652 #[test]
653 fn first_mismatch_v1_v1_different_version() {
654 let mut v1_b = make_v1();
655 v1_b.version = 42;
656 let a = DataContractInSerializationFormat::V1(make_v1());
657 let b = DataContractInSerializationFormat::V1(v1_b);
658 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Version));
659 }
660
661 #[test]
662 fn first_mismatch_v1_v1_different_owner_id() {
663 let mut v1_b = make_v1();
664 v1_b.owner_id = Identifier::from([3u8; 32]);
665 let a = DataContractInSerializationFormat::V1(make_v1());
666 let b = DataContractInSerializationFormat::V1(v1_b);
667 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::OwnerId));
668 }
669
670 #[test]
671 fn first_mismatch_v1_v1_different_schema_defs() {
672 let mut v1_b = make_v1();
673 let mut defs = BTreeMap::new();
674 defs.insert("someDef".to_string(), Value::Bool(true));
675 v1_b.schema_defs = Some(defs);
676 let a = DataContractInSerializationFormat::V1(make_v1());
677 let b = DataContractInSerializationFormat::V1(v1_b);
678 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::SchemaDefs));
679 }
680
681 #[test]
682 fn first_mismatch_v1_v1_different_document_schemas() {
683 let mut v1_b = make_v1();
684 v1_b.document_schemas
685 .insert("doc".to_string(), Value::U64(1));
686 let a = DataContractInSerializationFormat::V1(make_v1());
687 let b = DataContractInSerializationFormat::V1(v1_b);
688 assert_eq!(
689 a.first_mismatch(&b),
690 Some(DataContractMismatch::DocumentSchemas)
691 );
692 }
693
694 #[test]
695 fn first_mismatch_v1_v1_different_groups() {
696 let mut v1_b = make_v1();
697 v1_b.groups.insert(
698 0,
699 Group::V0(GroupV0 {
700 members: Default::default(),
701 required_power: 1,
702 }),
703 );
704 let a = DataContractInSerializationFormat::V1(make_v1());
705 let b = DataContractInSerializationFormat::V1(v1_b);
706 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Groups));
707 }
708
709 #[test]
710 fn first_mismatch_v1_v1_different_tokens() {
711 let mut v1_b = make_v1();
712 v1_b.tokens.insert(
713 0,
714 TokenConfiguration::V0(
715 crate::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0::default_most_restrictive(),
716 ),
717 );
718 let a = DataContractInSerializationFormat::V1(make_v1());
719 let b = DataContractInSerializationFormat::V1(v1_b);
720 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Tokens));
721 }
722
723 #[test]
724 fn first_mismatch_v1_v1_different_keywords() {
725 let mut v1_b = make_v1();
726 v1_b.keywords = vec!["test".to_string()];
727 let a = DataContractInSerializationFormat::V1(make_v1());
728 let b = DataContractInSerializationFormat::V1(v1_b);
729 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
730 }
731
732 #[test]
733 fn first_mismatch_v1_v1_keywords_case_insensitive_match() {
734 let mut v1_a = make_v1();
735 v1_a.keywords = vec!["Test".to_string()];
736 let mut v1_b = make_v1();
737 v1_b.keywords = vec!["test".to_string()];
738 let a = DataContractInSerializationFormat::V1(v1_a);
739 let b = DataContractInSerializationFormat::V1(v1_b);
740 assert_eq!(a.first_mismatch(&b), None);
742 }
743
744 #[test]
745 fn first_mismatch_v1_v1_keywords_different_length() {
746 let mut v1_a = make_v1();
747 v1_a.keywords = vec!["a".to_string()];
748 let mut v1_b = make_v1();
749 v1_b.keywords = vec!["a".to_string(), "b".to_string()];
750 let a = DataContractInSerializationFormat::V1(v1_a);
751 let b = DataContractInSerializationFormat::V1(v1_b);
752 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
753 }
754
755 #[test]
756 fn first_mismatch_v1_v1_different_description() {
757 let mut v1_b = make_v1();
758 v1_b.description = Some("a description".to_string());
759 let a = DataContractInSerializationFormat::V1(make_v1());
760 let b = DataContractInSerializationFormat::V1(v1_b);
761 assert_eq!(
762 a.first_mismatch(&b),
763 Some(DataContractMismatch::Description)
764 );
765 }
766
767 #[test]
772 fn first_mismatch_v1_v1_id_takes_priority_over_config() {
773 let mut v1_b = make_v1();
774 v1_b.id = Identifier::from([5u8; 32]);
775 let mut cfg = DataContractConfigV1::default();
776 cfg.readonly = !cfg.readonly;
777 v1_b.config = DataContractConfig::V1(cfg);
778 let a = DataContractInSerializationFormat::V1(make_v1());
779 let b = DataContractInSerializationFormat::V1(v1_b);
780 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
782 }
783
784 #[test]
789 fn data_contract_mismatch_display() {
790 assert_eq!(format!("{}", DataContractMismatch::Id), "ID fields differ");
791 assert_eq!(
792 format!("{}", DataContractMismatch::FormatVersionMismatch),
793 "Serialization format versions differ (e.g., V0 vs V1)"
794 );
795 assert_eq!(
796 format!("{}", DataContractMismatch::V0Mismatch),
797 "V0 versions differ"
798 );
799 assert_eq!(format!("{}", DataContractMismatch::Tokens), "Tokens differ");
800 assert_eq!(
801 format!("{}", DataContractMismatch::Keywords),
802 "Keywords differ"
803 );
804 assert_eq!(
805 format!("{}", DataContractMismatch::Description),
806 "Description fields differ"
807 );
808 }
809
810 #[test]
815 fn accessor_id_v0() {
816 let v0 = make_v0();
817 let expected_id = v0.id;
818 let format = DataContractInSerializationFormat::V0(v0);
819 assert_eq!(format.id(), expected_id);
820 }
821
822 #[test]
823 fn accessor_id_v1() {
824 let v1 = make_v1();
825 let expected_id = v1.id;
826 let format = DataContractInSerializationFormat::V1(v1);
827 assert_eq!(format.id(), expected_id);
828 }
829
830 #[test]
831 fn accessor_owner_id_v0() {
832 let mut v0 = make_v0();
833 v0.owner_id = Identifier::from([7u8; 32]);
834 let expected = v0.owner_id;
835 let format = DataContractInSerializationFormat::V0(v0);
836 assert_eq!(format.owner_id(), expected);
837 }
838
839 #[test]
840 fn accessor_version_v0() {
841 let mut v0 = make_v0();
842 v0.version = 10;
843 let format = DataContractInSerializationFormat::V0(v0);
844 assert_eq!(format.version(), 10);
845 }
846
847 #[test]
848 fn accessor_version_v1() {
849 let mut v1 = make_v1();
850 v1.version = 20;
851 let format = DataContractInSerializationFormat::V1(v1);
852 assert_eq!(format.version(), 20);
853 }
854
855 #[test]
856 fn accessor_groups_v0_returns_empty() {
857 let format = DataContractInSerializationFormat::V0(make_v0());
858 assert!(format.groups().is_empty());
859 }
860
861 #[test]
862 fn accessor_tokens_v0_returns_empty() {
863 let format = DataContractInSerializationFormat::V0(make_v0());
864 assert!(format.tokens().is_empty());
865 }
866
867 #[test]
868 fn accessor_keywords_v0_returns_empty() {
869 let format = DataContractInSerializationFormat::V0(make_v0());
870 assert!(format.keywords().is_empty());
871 }
872
873 #[test]
874 fn accessor_description_v0_returns_none() {
875 let format = DataContractInSerializationFormat::V0(make_v0());
876 assert_eq!(format.description(), &None);
877 }
878
879 #[test]
880 fn accessor_keywords_v1() {
881 let mut v1 = make_v1();
882 v1.keywords = vec!["hello".to_string()];
883 let format = DataContractInSerializationFormat::V1(v1);
884 assert_eq!(format.keywords(), &vec!["hello".to_string()]);
885 }
886
887 #[test]
888 fn accessor_description_v1_some() {
889 let mut v1 = make_v1();
890 v1.description = Some("desc".to_string());
891 let format = DataContractInSerializationFormat::V1(v1);
892 assert_eq!(format.description(), &Some("desc".to_string()));
893 }
894
895 #[test]
896 fn accessor_document_schemas_v0() {
897 let mut v0 = make_v0();
898 v0.document_schemas
899 .insert("note".to_string(), Value::Bool(true));
900 let format = DataContractInSerializationFormat::V0(v0);
901 assert_eq!(format.document_schemas().len(), 1);
902 assert!(format.document_schemas().contains_key("note"));
903 }
904
905 #[test]
906 fn accessor_schema_defs_v0_none() {
907 let format = DataContractInSerializationFormat::V0(make_v0());
908 assert!(format.schema_defs().is_none());
909 }
910
911 #[test]
912 fn accessor_schema_defs_v1_some() {
913 let mut v1 = make_v1();
914 let mut defs = BTreeMap::new();
915 defs.insert("def1".to_string(), Value::Null);
916 v1.schema_defs = Some(defs);
917 let format = DataContractInSerializationFormat::V1(v1);
918 assert!(format.schema_defs().is_some());
919 assert!(format.schema_defs().unwrap().contains_key("def1"));
920 }
921
922 #[test]
927 fn try_from_platform_versioned_data_contract_v0_version_0() {
928 let platform_version = PlatformVersion::first();
929 let v0 = DataContractV0 {
931 id: Identifier::from([10u8; 32]),
932 config: DataContractConfig::V0(DataContractConfigV0::default()),
933 version: 1,
934 owner_id: Identifier::from([20u8; 32]),
935 schema_defs: None,
936 document_types: BTreeMap::new(),
937 metadata: None,
938 };
939 let result = DataContractInSerializationFormat::try_from_platform_versioned(
940 v0.clone(),
941 platform_version,
942 );
943 assert!(result.is_ok());
944 let format = result.unwrap();
945 assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
946 assert_eq!(format.id(), Identifier::from([10u8; 32]));
947 assert_eq!(format.owner_id(), Identifier::from([20u8; 32]));
948 }
949
950 #[test]
951 fn try_from_platform_versioned_data_contract_v0_ref_version_0() {
952 let platform_version = PlatformVersion::first();
953 let v0 = DataContractV0 {
954 id: Identifier::from([11u8; 32]),
955 config: DataContractConfig::V0(DataContractConfigV0::default()),
956 version: 2,
957 owner_id: Identifier::from([22u8; 32]),
958 schema_defs: None,
959 document_types: BTreeMap::new(),
960 metadata: None,
961 };
962 let result =
963 DataContractInSerializationFormat::try_from_platform_versioned(&v0, platform_version);
964 assert!(result.is_ok());
965 let format = result.unwrap();
966 assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
967 assert_eq!(format.version(), 2);
968 }
969
970 #[test]
971 fn try_from_platform_versioned_data_contract_v0_version_1() {
972 let platform_version = PlatformVersion::latest();
973 let v0 = DataContractV0 {
975 id: Identifier::from([10u8; 32]),
976 config: DataContractConfig::V0(DataContractConfigV0::default()),
977 version: 1,
978 owner_id: Identifier::from([20u8; 32]),
979 schema_defs: None,
980 document_types: BTreeMap::new(),
981 metadata: None,
982 };
983 let result = DataContractInSerializationFormat::try_from_platform_versioned(
984 v0.clone(),
985 platform_version,
986 );
987 assert!(result.is_ok());
988 let format = result.unwrap();
989 assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
990 }
991
992 #[test]
997 fn try_from_platform_versioned_data_contract_v1_version_0() {
998 let platform_version = PlatformVersion::first();
999 let v1 = DataContractV1 {
1000 id: Identifier::from([10u8; 32]),
1001 config: DataContractConfig::V0(DataContractConfigV0::default()),
1002 version: 1,
1003 owner_id: Identifier::from([20u8; 32]),
1004 schema_defs: None,
1005 document_types: BTreeMap::new(),
1006 created_at: None,
1007 updated_at: None,
1008 created_at_block_height: None,
1009 updated_at_block_height: None,
1010 created_at_epoch: None,
1011 updated_at_epoch: None,
1012 groups: BTreeMap::new(),
1013 tokens: BTreeMap::new(),
1014 keywords: vec![],
1015 description: None,
1016 };
1017 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1018 v1.clone(),
1019 platform_version,
1020 );
1021 assert!(result.is_ok());
1022 let format = result.unwrap();
1023 assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1024 }
1025
1026 #[test]
1027 fn try_from_platform_versioned_data_contract_v1_version_1() {
1028 let platform_version = PlatformVersion::latest();
1029 let v1 = DataContractV1 {
1030 id: Identifier::from([10u8; 32]),
1031 config: DataContractConfig::V1(DataContractConfigV1::default()),
1032 version: 1,
1033 owner_id: Identifier::from([20u8; 32]),
1034 schema_defs: None,
1035 document_types: BTreeMap::new(),
1036 created_at: None,
1037 updated_at: None,
1038 created_at_block_height: None,
1039 updated_at_block_height: None,
1040 created_at_epoch: None,
1041 updated_at_epoch: None,
1042 groups: BTreeMap::new(),
1043 tokens: BTreeMap::new(),
1044 keywords: vec![],
1045 description: None,
1046 };
1047 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1048 v1.clone(),
1049 platform_version,
1050 );
1051 assert!(result.is_ok());
1052 let format = result.unwrap();
1053 assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1054 }
1055
1056 #[test]
1057 fn try_from_platform_versioned_data_contract_v1_ref_version_1() {
1058 let platform_version = PlatformVersion::latest();
1059 let v1 = DataContractV1 {
1060 id: Identifier::from([10u8; 32]),
1061 config: DataContractConfig::V1(DataContractConfigV1::default()),
1062 version: 3,
1063 owner_id: Identifier::from([20u8; 32]),
1064 schema_defs: None,
1065 document_types: BTreeMap::new(),
1066 created_at: None,
1067 updated_at: None,
1068 created_at_block_height: None,
1069 updated_at_block_height: None,
1070 created_at_epoch: None,
1071 updated_at_epoch: None,
1072 groups: BTreeMap::new(),
1073 tokens: BTreeMap::new(),
1074 keywords: vec![],
1075 description: None,
1076 };
1077 let result =
1078 DataContractInSerializationFormat::try_from_platform_versioned(&v1, platform_version);
1079 assert!(result.is_ok());
1080 let format = result.unwrap();
1081 assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1082 assert_eq!(format.version(), 3);
1083 }
1084
1085 #[test]
1090 fn try_from_platform_versioned_data_contract_ref_version_0() {
1091 let platform_version = PlatformVersion::first();
1092 let contract = DataContract::V0(DataContractV0 {
1093 id: Identifier::from([10u8; 32]),
1094 config: DataContractConfig::V0(DataContractConfigV0::default()),
1095 version: 1,
1096 owner_id: Identifier::from([20u8; 32]),
1097 schema_defs: None,
1098 document_types: BTreeMap::new(),
1099 metadata: None,
1100 });
1101 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1102 &contract,
1103 platform_version,
1104 );
1105 assert!(result.is_ok());
1106 assert!(matches!(
1107 result.unwrap(),
1108 DataContractInSerializationFormat::V0(_)
1109 ));
1110 }
1111
1112 #[test]
1113 fn try_from_platform_versioned_data_contract_owned_version_1() {
1114 let platform_version = PlatformVersion::latest();
1115 let contract = DataContract::V0(DataContractV0 {
1116 id: Identifier::from([10u8; 32]),
1117 config: DataContractConfig::V0(DataContractConfigV0::default()),
1118 version: 1,
1119 owner_id: Identifier::from([20u8; 32]),
1120 schema_defs: None,
1121 document_types: BTreeMap::new(),
1122 metadata: None,
1123 });
1124 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1125 contract,
1126 platform_version,
1127 );
1128 assert!(result.is_ok());
1129 assert!(matches!(
1130 result.unwrap(),
1131 DataContractInSerializationFormat::V1(_)
1132 ));
1133 }
1134
1135 #[test]
1140 fn first_platform_version_uses_serialization_version_0() {
1141 let pv = PlatformVersion::first();
1142 assert_eq!(
1143 pv.dpp
1144 .contract_versions
1145 .contract_serialization_version
1146 .default_current_version,
1147 0
1148 );
1149 }
1150
1151 #[test]
1152 fn latest_platform_version_uses_serialization_version_1() {
1153 let pv = PlatformVersion::latest();
1154 assert_eq!(
1155 pv.dpp
1156 .contract_versions
1157 .contract_serialization_version
1158 .default_current_version,
1159 1
1160 );
1161 }
1162
1163 #[test]
1164 fn first_platform_version_uses_contract_structure_0() {
1165 let pv = PlatformVersion::first();
1166 assert_eq!(pv.dpp.contract_versions.contract_structure_version, 0);
1167 }
1168
1169 #[test]
1170 fn latest_platform_version_uses_contract_structure_1() {
1171 let pv = PlatformVersion::latest();
1172 assert_eq!(pv.dpp.contract_versions.contract_structure_version, 1);
1173 }
1174}
1175
1176#[cfg(all(
1177 test,
1178 feature = "json-conversion",
1179 feature = "value-conversion",
1180 feature = "serde-conversion"
1181))]
1182mod json_convertible_tests {
1183 use super::*;
1184 use crate::data_contract::config::v0::DataContractConfigV0;
1185 use crate::data_contract::config::DataContractConfig;
1186 use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
1187 use platform_value::Identifier;
1188 use std::collections::BTreeMap;
1189
1190 fn fixture() -> DataContractInSerializationFormat {
1191 DataContractInSerializationFormat::V0(DataContractInSerializationFormatV0 {
1192 id: Identifier::new([0xa1; 32]),
1193 config: DataContractConfig::V0(DataContractConfigV0::default()),
1194 version: 1,
1195 owner_id: Identifier::new([0xb2; 32]),
1196 schema_defs: None,
1197 document_schemas: BTreeMap::new(),
1198 })
1199 }
1200
1201 #[test]
1202 fn json_round_trip_with_full_wire_shape() {
1203 use crate::serialization::JsonConvertible;
1204 use serde_json::json;
1205 let original = fixture();
1206 let json = original.to_json().expect("to_json");
1207 assert_eq!(json["$formatVersion"], "0");
1214 assert_eq!(
1215 json["id"],
1216 json!("Bswb3UyeD1pUTaGiE6WvqwFpJZsQSEY1xhJePCDTHdvp")
1217 );
1218 assert_eq!(
1219 json["ownerId"],
1220 json!("D2ZcUbtpG5sKq7XLeB4YnpNnTGSptKCxTddoNeydzJQq")
1221 );
1222 assert_eq!(json["version"], json!(1));
1223 assert_eq!(json["schemaDefs"], json!(null));
1224 assert_eq!(json["documentSchemas"], json!({}));
1225 assert!(json.get("config").is_some(), "config envelope present");
1226 assert_eq!(json["config"]["$formatVersion"], "0");
1227 let recovered = DataContractInSerializationFormat::from_json(json).expect("from_json");
1228 assert_eq!(original, recovered);
1229 }
1230
1231 #[test]
1232 fn value_round_trip_with_full_wire_shape() {
1233 use crate::serialization::ValueConvertible;
1234 use platform_value::Value;
1235 let original = fixture();
1236 let value = original.to_object().expect("to_object");
1237 let map = match &value {
1240 Value::Map(m) => m,
1241 other => panic!("expected Value::Map, got {:?}", other),
1242 };
1243 let get = |k: &str| -> &Value {
1244 map.iter()
1245 .find(|(key, _)| matches!(key, Value::Text(t) if t == k))
1246 .map(|(_, v)| v)
1247 .unwrap_or_else(|| panic!("missing key {k}"))
1248 };
1249 assert_eq!(get("$formatVersion"), &Value::Text("0".to_string()));
1250 assert_eq!(get("id"), &Value::Identifier([0xa1; 32]));
1251 assert_eq!(get("ownerId"), &Value::Identifier([0xb2; 32]));
1252 assert_eq!(get("version"), &Value::U32(1));
1253 assert_eq!(get("schemaDefs"), &Value::Null);
1254 assert!(matches!(get("documentSchemas"), Value::Map(m) if m.is_empty()));
1256 assert!(matches!(get("config"), Value::Map(_)));
1258 let recovered = DataContractInSerializationFormat::from_object(value).expect("from_object");
1259 assert_eq!(original, recovered);
1260 }
1261
1262 #[test]
1263 fn json_preserves_format_version_tag() {
1264 use crate::serialization::JsonConvertible;
1265 let json = fixture().to_json().expect("to_json");
1266 assert_eq!(json["$formatVersion"], "0");
1267 }
1268}