1mod fields;
2mod methods;
3pub mod v0;
4pub mod v1;
5
6use crate::data_contract::config::v1::{
7 DataContractConfigGettersV1, DataContractConfigSettersV1, DataContractConfigV1,
8};
9use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
10#[cfg(feature = "json-conversion")]
11use crate::serialization::JsonConvertible;
12#[cfg(feature = "value-conversion")]
13use crate::serialization::ValueConvertible;
14use crate::version::PlatformVersion;
15use crate::ProtocolError;
16use bincode::{Decode, Encode};
17use derive_more::From;
18pub use fields::*;
19use platform_value::Value;
20use serde::{Deserialize, Serialize};
21use std::collections::BTreeMap;
22use v0::{DataContractConfigGettersV0, DataContractConfigSettersV0, DataContractConfigV0};
23
24#[cfg_attr(feature = "json-conversion", derive(JsonConvertible))]
25#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
26#[derive(Serialize, Deserialize, Encode, Decode, Debug, Clone, Copy, PartialEq, Eq, From)]
27#[serde(tag = "$formatVersion")]
28pub enum DataContractConfig {
29 #[serde(rename = "0")]
30 V0(DataContractConfigV0),
31 #[serde(rename = "1")]
32 V1(DataContractConfigV1),
33}
34
35impl DataContractConfig {
36 pub fn version(&self) -> u16 {
37 match self {
38 DataContractConfig::V0(_) => 0,
39 DataContractConfig::V1(_) => 1,
40 }
41 }
42
43 pub fn default_for_version(
44 platform_version: &PlatformVersion,
45 ) -> Result<DataContractConfig, ProtocolError> {
46 match platform_version
47 .dpp
48 .contract_versions
49 .config
50 .default_current_version
51 {
52 0 => Ok(DataContractConfigV0::default().into()),
53 1 => Ok(DataContractConfigV1::default().into()),
54 version => Err(ProtocolError::UnknownVersionMismatch {
55 method: "DataContractConfig::default_for_version".to_string(),
56 known_versions: vec![0, 1],
57 received: version,
58 }),
59 }
60 }
61
62 pub fn config_valid_for_platform_version(
67 self,
68 platform_version: &PlatformVersion,
69 ) -> DataContractConfig {
70 match self {
71 DataContractConfig::V0(v0) => DataContractConfig::V0(v0),
72 DataContractConfig::V1(v1) => {
73 if platform_version.dpp.contract_versions.config.max_version == 0 {
74 DataContractConfig::V0(v1.into())
75 } else {
76 self
77 }
78 }
79 }
80 }
81
82 pub fn from_value(
88 value: Value,
89 platform_version: &PlatformVersion,
90 ) -> Result<DataContractConfig, ProtocolError> {
91 match platform_version
92 .dpp
93 .contract_versions
94 .config
95 .default_current_version
96 {
97 0 => {
98 let config: DataContractConfigV0 = platform_value::from_value(value)?;
99 Ok(config.into())
100 }
101 1 => {
102 let config: DataContractConfigV1 = platform_value::from_value(value)?;
103 Ok(config.into())
104 }
105 version => Err(ProtocolError::UnknownVersionMismatch {
106 method: "DataContractConfig::from_value".to_string(),
107 known_versions: vec![0, 1],
108 received: version,
109 }),
110 }
111 }
112
113 pub(in crate::data_contract) fn get_contract_configuration_properties(
133 contract: &BTreeMap<String, Value>,
134 platform_version: &PlatformVersion,
135 ) -> Result<DataContractConfig, ProtocolError> {
136 match platform_version
137 .dpp
138 .contract_versions
139 .config
140 .default_current_version
141 {
142 0 => Ok(
143 DataContractConfigV0::get_contract_configuration_properties_v0(contract)?.into(),
144 ),
145 1 => Ok(
146 DataContractConfigV1::get_contract_configuration_properties_v1(contract)?.into(),
147 ),
148 version => Err(ProtocolError::UnknownVersionMismatch {
149 method: "DataContractConfig::get_contract_configuration_properties".to_string(),
150 known_versions: vec![0, 1],
151 received: version,
152 }),
153 }
154 }
155}
156
157impl DataContractConfigGettersV0 for DataContractConfig {
158 fn can_be_deleted(&self) -> bool {
159 match self {
160 DataContractConfig::V0(v0) => v0.can_be_deleted,
161 DataContractConfig::V1(v1) => v1.can_be_deleted,
162 }
163 }
164
165 fn readonly(&self) -> bool {
166 match self {
167 DataContractConfig::V0(v0) => v0.readonly,
168 DataContractConfig::V1(v1) => v1.readonly,
169 }
170 }
171
172 fn keeps_history(&self) -> bool {
173 match self {
174 DataContractConfig::V0(v0) => v0.keeps_history,
175 DataContractConfig::V1(v1) => v1.keeps_history,
176 }
177 }
178
179 fn documents_keep_history_contract_default(&self) -> bool {
180 match self {
181 DataContractConfig::V0(v0) => v0.documents_keep_history_contract_default,
182 DataContractConfig::V1(v1) => v1.documents_keep_history_contract_default,
183 }
184 }
185
186 fn documents_mutable_contract_default(&self) -> bool {
187 match self {
188 DataContractConfig::V0(v0) => v0.documents_mutable_contract_default,
189 DataContractConfig::V1(v1) => v1.documents_mutable_contract_default,
190 }
191 }
192
193 fn documents_can_be_deleted_contract_default(&self) -> bool {
194 match self {
195 DataContractConfig::V0(v0) => v0.documents_can_be_deleted_contract_default,
196 DataContractConfig::V1(v1) => v1.documents_can_be_deleted_contract_default,
197 }
198 }
199
200 fn requires_identity_encryption_bounded_key(&self) -> Option<StorageKeyRequirements> {
202 match self {
203 DataContractConfig::V0(v0) => v0.requires_identity_encryption_bounded_key,
204 DataContractConfig::V1(v1) => v1.requires_identity_encryption_bounded_key,
205 }
206 }
207
208 fn requires_identity_decryption_bounded_key(&self) -> Option<StorageKeyRequirements> {
210 match self {
211 DataContractConfig::V0(v0) => v0.requires_identity_decryption_bounded_key,
212 DataContractConfig::V1(v1) => v1.requires_identity_decryption_bounded_key,
213 }
214 }
215}
216
217impl DataContractConfigSettersV0 for DataContractConfig {
218 fn set_can_be_deleted(&mut self, value: bool) {
219 match self {
220 DataContractConfig::V0(v0) => v0.can_be_deleted = value,
221 DataContractConfig::V1(v1) => v1.can_be_deleted = value,
222 }
223 }
224
225 fn set_readonly(&mut self, value: bool) {
226 match self {
227 DataContractConfig::V0(v0) => v0.readonly = value,
228 DataContractConfig::V1(v1) => v1.readonly = value,
229 }
230 }
231
232 fn set_keeps_history(&mut self, value: bool) {
233 match self {
234 DataContractConfig::V0(v0) => v0.keeps_history = value,
235 DataContractConfig::V1(v1) => v1.keeps_history = value,
236 }
237 }
238
239 fn set_documents_keep_history_contract_default(&mut self, value: bool) {
240 match self {
241 DataContractConfig::V0(v0) => v0.documents_keep_history_contract_default = value,
242 DataContractConfig::V1(v1) => v1.documents_keep_history_contract_default = value,
243 }
244 }
245
246 fn set_documents_can_be_deleted_contract_default(&mut self, value: bool) {
247 match self {
248 DataContractConfig::V0(v0) => v0.documents_can_be_deleted_contract_default = value,
249 DataContractConfig::V1(v1) => v1.documents_can_be_deleted_contract_default = value,
250 }
251 }
252
253 fn set_documents_mutable_contract_default(&mut self, value: bool) {
254 match self {
255 DataContractConfig::V0(v0) => v0.documents_mutable_contract_default = value,
256 DataContractConfig::V1(v1) => v1.documents_mutable_contract_default = value,
257 }
258 }
259
260 fn set_requires_identity_encryption_bounded_key(
261 &mut self,
262 value: Option<StorageKeyRequirements>,
263 ) {
264 match self {
265 DataContractConfig::V0(v0) => v0.requires_identity_encryption_bounded_key = value,
266 DataContractConfig::V1(v1) => v1.requires_identity_encryption_bounded_key = value,
267 }
268 }
269
270 fn set_requires_identity_decryption_bounded_key(
271 &mut self,
272 value: Option<StorageKeyRequirements>,
273 ) {
274 match self {
275 DataContractConfig::V0(v0) => v0.requires_identity_decryption_bounded_key = value,
276 DataContractConfig::V1(v1) => v1.requires_identity_decryption_bounded_key = value,
277 }
278 }
279}
280
281impl DataContractConfigGettersV1 for DataContractConfig {
282 fn sized_integer_types(&self) -> bool {
283 match self {
284 DataContractConfig::V0(_) => false,
285 DataContractConfig::V1(v1) => v1.sized_integer_types,
286 }
287 }
288}
289
290impl DataContractConfigSettersV1 for DataContractConfig {
291 fn set_sized_integer_types_enabled(&mut self, enable: bool) {
292 match self {
293 DataContractConfig::V0(_) => {}
294 DataContractConfig::V1(v1) => v1.sized_integer_types = enable,
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use crate::data_contract::config::v0::DataContractConfigV0;
303 use crate::data_contract::config::v1::DataContractConfigV1;
304 use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
305 use platform_version::version::PlatformVersion;
306
307 mod default_for_version {
308 use super::*;
309
310 #[test]
311 fn default_for_latest_platform_version() {
312 let platform_version = PlatformVersion::latest();
313 let config = DataContractConfig::default_for_version(platform_version)
314 .expect("should create config for latest version");
315
316 let expected_version = platform_version
318 .dpp
319 .contract_versions
320 .config
321 .default_current_version;
322
323 assert_eq!(config.version(), expected_version);
324 }
325
326 #[test]
327 fn default_for_first_platform_version() {
328 let platform_version = PlatformVersion::first();
329 let config = DataContractConfig::default_for_version(platform_version)
330 .expect("should create config for first version");
331
332 let expected_version = platform_version
333 .dpp
334 .contract_versions
335 .config
336 .default_current_version;
337
338 assert_eq!(config.version(), expected_version);
339 }
340 }
341
342 mod version_method {
343 use super::*;
344
345 #[test]
346 fn v0_reports_version_0() {
347 let config = DataContractConfig::V0(DataContractConfigV0::default());
348 assert_eq!(config.version(), 0);
349 }
350
351 #[test]
352 fn v1_reports_version_1() {
353 let config = DataContractConfig::V1(DataContractConfigV1::default());
354 assert_eq!(config.version(), 1);
355 }
356 }
357
358 mod from_conversions {
359 use super::*;
360
361 #[test]
362 fn v0_into_config() {
363 let v0 = DataContractConfigV0::default();
364 let config: DataContractConfig = v0.into();
365 assert_eq!(config.version(), 0);
366 }
367
368 #[test]
369 fn v1_into_config() {
370 let v1 = DataContractConfigV1::default();
371 let config: DataContractConfig = v1.into();
372 assert_eq!(config.version(), 1);
373 }
374
375 #[test]
376 fn v1_to_v0_conversion_preserves_fields() {
377 let v1 = DataContractConfigV1 {
378 can_be_deleted: true,
379 readonly: true,
380 keeps_history: true,
381 documents_keep_history_contract_default: true,
382 documents_mutable_contract_default: false,
383 documents_can_be_deleted_contract_default: false,
384 requires_identity_encryption_bounded_key: None,
385 requires_identity_decryption_bounded_key: None,
386 sized_integer_types: true,
387 };
388 let v0: DataContractConfigV0 = v1.into();
389 assert!(v0.can_be_deleted);
390 assert!(v0.readonly);
391 assert!(v0.keeps_history);
392 assert!(v0.documents_keep_history_contract_default);
393 assert!(!v0.documents_mutable_contract_default);
394 assert!(!v0.documents_can_be_deleted_contract_default);
395 }
396 }
397
398 mod getters_v0 {
399 use super::*;
400
401 #[test]
402 fn default_v0_getter_values() {
403 let config = DataContractConfig::V0(DataContractConfigV0::default());
404 assert_eq!(config.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
405 assert_eq!(config.readonly(), !DEFAULT_CONTRACT_MUTABILITY);
406 assert_eq!(config.keeps_history(), DEFAULT_CONTRACT_KEEPS_HISTORY);
407 assert_eq!(
408 config.documents_keep_history_contract_default(),
409 DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY
410 );
411 assert_eq!(
412 config.documents_mutable_contract_default(),
413 DEFAULT_CONTRACT_DOCUMENT_MUTABILITY
414 );
415 assert_eq!(
416 config.documents_can_be_deleted_contract_default(),
417 DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED
418 );
419 assert!(config.requires_identity_encryption_bounded_key().is_none());
420 assert!(config.requires_identity_decryption_bounded_key().is_none());
421 }
422
423 #[test]
424 fn default_v1_getter_values() {
425 let config = DataContractConfig::V1(DataContractConfigV1::default());
426 assert_eq!(config.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
427 assert_eq!(config.readonly(), !DEFAULT_CONTRACT_MUTABILITY);
428 assert_eq!(config.keeps_history(), DEFAULT_CONTRACT_KEEPS_HISTORY);
429 assert_eq!(
430 config.documents_keep_history_contract_default(),
431 DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY
432 );
433 assert_eq!(
434 config.documents_mutable_contract_default(),
435 DEFAULT_CONTRACT_DOCUMENT_MUTABILITY
436 );
437 assert_eq!(
438 config.documents_can_be_deleted_contract_default(),
439 DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED
440 );
441 }
442 }
443
444 mod setters_v0 {
445 use super::*;
446
447 #[test]
448 fn set_can_be_deleted_on_v0() {
449 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
450 config.set_can_be_deleted(true);
451 assert!(config.can_be_deleted());
452 config.set_can_be_deleted(false);
453 assert!(!config.can_be_deleted());
454 }
455
456 #[test]
457 fn set_readonly_on_v1() {
458 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
459 config.set_readonly(true);
460 assert!(config.readonly());
461 config.set_readonly(false);
462 assert!(!config.readonly());
463 }
464
465 #[test]
466 fn set_keeps_history() {
467 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
468 config.set_keeps_history(true);
469 assert!(config.keeps_history());
470 }
471
472 #[test]
473 fn set_documents_keep_history() {
474 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
475 config.set_documents_keep_history_contract_default(true);
476 assert!(config.documents_keep_history_contract_default());
477 }
478
479 #[test]
480 fn set_documents_mutable() {
481 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
482 config.set_documents_mutable_contract_default(false);
483 assert!(!config.documents_mutable_contract_default());
484 }
485
486 #[test]
487 fn set_documents_can_be_deleted() {
488 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
489 config.set_documents_can_be_deleted_contract_default(false);
490 assert!(!config.documents_can_be_deleted_contract_default());
491 }
492
493 #[test]
494 fn set_encryption_key_requirements() {
495 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
496 config
497 .set_requires_identity_encryption_bounded_key(Some(StorageKeyRequirements::Unique));
498 assert_eq!(
499 config.requires_identity_encryption_bounded_key(),
500 Some(StorageKeyRequirements::Unique)
501 );
502 }
503
504 #[test]
505 fn set_decryption_key_requirements() {
506 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
507 config
508 .set_requires_identity_decryption_bounded_key(Some(StorageKeyRequirements::Unique));
509 assert_eq!(
510 config.requires_identity_decryption_bounded_key(),
511 Some(StorageKeyRequirements::Unique)
512 );
513 }
514 }
515
516 mod getters_setters_v1 {
517 use super::*;
518
519 #[test]
520 fn sized_integer_types_default_v1() {
521 let config = DataContractConfig::V1(DataContractConfigV1::default());
522 assert!(config.sized_integer_types());
524 }
525
526 #[test]
527 fn sized_integer_types_v0_always_false() {
528 let config = DataContractConfig::V0(DataContractConfigV0::default());
529 assert!(!config.sized_integer_types());
530 }
531
532 #[test]
533 fn set_sized_integer_types_on_v1() {
534 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
535 config.set_sized_integer_types_enabled(false);
536 assert!(!config.sized_integer_types());
537 config.set_sized_integer_types_enabled(true);
538 assert!(config.sized_integer_types());
539 }
540
541 #[test]
542 fn set_sized_integer_types_on_v0_is_noop() {
543 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
544 config.set_sized_integer_types_enabled(true);
545 assert!(!config.sized_integer_types());
547 }
548 }
549
550 mod config_valid_for_platform_version {
551 use super::*;
552
553 #[test]
554 fn v0_stays_v0_regardless_of_platform() {
555 let config = DataContractConfig::V0(DataContractConfigV0::default());
556 let result = config.config_valid_for_platform_version(PlatformVersion::latest());
557 assert_eq!(result.version(), 0);
558 }
559
560 #[test]
561 fn v1_downgraded_to_v0_when_max_version_is_0() {
562 let config = DataContractConfig::V1(DataContractConfigV1 {
563 can_be_deleted: true,
564 readonly: false,
565 keeps_history: true,
566 documents_keep_history_contract_default: false,
567 documents_mutable_contract_default: true,
568 documents_can_be_deleted_contract_default: true,
569 requires_identity_encryption_bounded_key: None,
570 requires_identity_decryption_bounded_key: None,
571 sized_integer_types: true,
572 });
573
574 let platform_version = PlatformVersion::first();
576 if platform_version.dpp.contract_versions.config.max_version == 0 {
577 let result = config.config_valid_for_platform_version(platform_version);
578 assert_eq!(result.version(), 0);
579 assert!(result.can_be_deleted());
581 }
582 }
583
584 #[test]
585 fn v1_stays_v1_when_max_version_is_1_or_higher() {
586 let config = DataContractConfig::V1(DataContractConfigV1::default());
587 let platform_version = PlatformVersion::latest();
588 if platform_version.dpp.contract_versions.config.max_version >= 1 {
589 let result = config.config_valid_for_platform_version(platform_version);
590 assert_eq!(result.version(), 1);
591 }
592 }
593 }
594
595 mod get_contract_configuration_properties_v0_consensus_lock {
603 use super::*;
604 use crate::data_contract::config::property::{
605 REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY, REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY,
606 };
607 use platform_value::Value;
608 use std::collections::BTreeMap;
609
610 #[test]
614 fn encryption_property_populates_both_fields() {
615 let mut map: BTreeMap<String, Value> = BTreeMap::new();
616 map.insert(
617 REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY.to_string(),
618 Value::U8(StorageKeyRequirements::Unique as u8),
619 );
620
621 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
622 .expect("should parse V0 config");
623
624 assert_eq!(
625 config.requires_identity_encryption_bounded_key,
626 Some(StorageKeyRequirements::Unique)
627 );
628 assert_eq!(
629 config.requires_identity_decryption_bounded_key,
630 Some(StorageKeyRequirements::Unique),
631 "V0 consensus quirk: decryption field is read from the ENCRYPTION key"
632 );
633 }
634
635 #[test]
639 fn decryption_property_is_ignored_by_v0() {
640 let mut map: BTreeMap<String, Value> = BTreeMap::new();
641 map.insert(
642 REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY.to_string(),
643 Value::U8(StorageKeyRequirements::MultipleReferenceToLatest as u8),
644 );
645
646 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
647 .expect("should parse V0 config");
648
649 assert!(
650 config.requires_identity_encryption_bounded_key.is_none(),
651 "V0 does not read the DECRYPTION property at all"
652 );
653 assert!(
654 config.requires_identity_decryption_bounded_key.is_none(),
655 "V0 consensus quirk: decryption field is NOT sourced from the DECRYPTION key"
656 );
657 }
658
659 #[test]
662 fn encryption_wins_when_both_properties_set() {
663 let mut map: BTreeMap<String, Value> = BTreeMap::new();
664 map.insert(
665 REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY.to_string(),
666 Value::U8(StorageKeyRequirements::Unique as u8),
667 );
668 map.insert(
669 REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY.to_string(),
670 Value::U8(StorageKeyRequirements::Multiple as u8),
671 );
672
673 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
674 .expect("should parse V0 config");
675
676 assert_eq!(
677 config.requires_identity_encryption_bounded_key,
678 Some(StorageKeyRequirements::Unique)
679 );
680 assert_eq!(
681 config.requires_identity_decryption_bounded_key,
682 Some(StorageKeyRequirements::Unique),
683 "V0 consensus quirk: the DECRYPTION property is ignored"
684 );
685 }
686
687 #[test]
689 fn neither_property_set_leaves_both_none() {
690 let map: BTreeMap<String, Value> = BTreeMap::new();
691 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
692 .expect("should parse V0 config with defaults");
693 assert!(config.requires_identity_encryption_bounded_key.is_none());
694 assert!(config.requires_identity_decryption_bounded_key.is_none());
695 }
696 }
697
698 mod bincode_roundtrip {
699 use super::*;
700 use bincode::config;
701
702 #[test]
703 fn v0_bincode_roundtrip_preserves_fields() {
704 let cfg = config::standard();
705 let original = DataContractConfig::V0(DataContractConfigV0 {
706 can_be_deleted: true,
707 readonly: true,
708 keeps_history: true,
709 documents_keep_history_contract_default: true,
710 documents_mutable_contract_default: false,
711 documents_can_be_deleted_contract_default: false,
712 requires_identity_encryption_bounded_key: Some(StorageKeyRequirements::Unique),
713 requires_identity_decryption_bounded_key: None,
714 });
715 let bytes = bincode::encode_to_vec(original, cfg).expect("encode");
716 let (decoded, _): (DataContractConfig, _) =
717 bincode::decode_from_slice(&bytes, cfg).expect("decode");
718 assert_eq!(decoded, original);
719 }
720
721 #[test]
722 fn v1_bincode_roundtrip_preserves_sized_integer_types() {
723 let cfg = config::standard();
724 let original = DataContractConfig::V1(DataContractConfigV1 {
725 can_be_deleted: false,
726 readonly: false,
727 keeps_history: false,
728 documents_keep_history_contract_default: false,
729 documents_mutable_contract_default: true,
730 documents_can_be_deleted_contract_default: true,
731 requires_identity_encryption_bounded_key: None,
732 requires_identity_decryption_bounded_key: None,
733 sized_integer_types: false,
734 });
735 let bytes = bincode::encode_to_vec(original, cfg).expect("encode");
736 let (decoded, _): (DataContractConfig, _) =
737 bincode::decode_from_slice(&bytes, cfg).expect("decode");
738 assert_eq!(decoded, original);
739 assert!(!decoded.sized_integer_types());
741 }
742 }
743
744 mod from_value_tests {
745 use super::*;
746 use platform_value::platform_value;
747
748 #[test]
749 fn from_value_yields_default_for_empty_object() {
750 let value = platform_value!({});
752 let platform_version = PlatformVersion::latest();
753 let cfg = DataContractConfig::from_value(value, platform_version)
754 .expect("empty object should deserialize to defaults");
755 assert_eq!(cfg.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
757 }
758 }
759
760 mod get_contract_configuration_properties_tests {
761 use super::*;
762 use platform_value::Value;
763 use std::collections::BTreeMap;
764
765 fn make_contract_map(can_be_deleted: bool, readonly: bool) -> BTreeMap<String, Value> {
766 let mut m = BTreeMap::new();
767 m.insert(
768 property::CAN_BE_DELETED.to_string(),
769 Value::Bool(can_be_deleted),
770 );
771 m.insert(property::READONLY.to_string(), Value::Bool(readonly));
772 m
773 }
774
775 #[test]
776 fn reads_booleans_from_map() {
777 let platform_version = PlatformVersion::latest();
778 for (can_be_deleted, readonly) in [(true, false), (false, true)] {
781 let contract = make_contract_map(can_be_deleted, readonly);
782 let cfg = DataContractConfig::get_contract_configuration_properties(
783 &contract,
784 platform_version,
785 )
786 .expect("should parse config from map");
787 assert_eq!(cfg.can_be_deleted(), can_be_deleted);
788 assert_eq!(cfg.readonly(), readonly);
789 }
790 }
791
792 #[test]
793 fn missing_keys_fall_back_to_defaults() {
794 let platform_version = PlatformVersion::latest();
795 let empty: BTreeMap<String, Value> = BTreeMap::new();
796 let cfg =
797 DataContractConfig::get_contract_configuration_properties(&empty, platform_version)
798 .expect("should parse empty contract map");
799 assert_eq!(cfg.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
801 assert_eq!(cfg.keeps_history(), DEFAULT_CONTRACT_KEEPS_HISTORY);
802 }
803
804 #[test]
805 fn non_bool_value_errors() {
806 let platform_version = PlatformVersion::latest();
807 let mut m: BTreeMap<String, Value> = BTreeMap::new();
808 m.insert(
809 property::CAN_BE_DELETED.to_string(),
810 Value::Text("not-a-bool".to_string()),
811 );
812 let result =
813 DataContractConfig::get_contract_configuration_properties(&m, platform_version);
814 assert!(result.is_err());
815 }
816 }
817}
818
819#[cfg(all(
820 test,
821 feature = "json-conversion",
822 feature = "value-conversion",
823 feature = "serde-conversion"
824))]
825mod json_convertible_tests {
826 use super::*;
827 use crate::data_contract::config::v0::DataContractConfigV0;
828 use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
829 use platform_value::platform_value;
830 use serde_json::json;
831
832 fn fixture() -> DataContractConfig {
835 DataContractConfig::V0(DataContractConfigV0 {
836 can_be_deleted: true,
837 readonly: true,
838 keeps_history: true,
839 documents_keep_history_contract_default: true,
840 documents_mutable_contract_default: false,
841 documents_can_be_deleted_contract_default: false,
842 requires_identity_encryption_bounded_key: Some(StorageKeyRequirements::Unique),
843 requires_identity_decryption_bounded_key: Some(StorageKeyRequirements::Multiple),
844 })
845 }
846
847 #[test]
848 fn json_round_trip_with_full_wire_shape() {
849 use crate::serialization::JsonConvertible;
850 let original = fixture();
851 let json = original.to_json().expect("to_json");
852 assert_eq!(
858 json,
859 json!({
860 "$formatVersion": "0",
861 "canBeDeleted": true,
862 "readonly": true,
863 "keepsHistory": true,
864 "documentsKeepHistoryContractDefault": true,
865 "documentsMutableContractDefault": false,
866 "documentsCanBeDeletedContractDefault": false,
867 "requiresIdentityEncryptionBoundedKey": 0,
868 "requiresIdentityDecryptionBoundedKey": 1,
869 })
870 );
871 let recovered = DataContractConfig::from_json(json).expect("from_json");
872 assert_eq!(original, recovered);
873 }
874
875 #[test]
876 fn value_round_trip_with_full_wire_shape() {
877 use crate::serialization::ValueConvertible;
878 let original = fixture();
879 let value = original.to_object().expect("to_object");
880 assert_eq!(
883 value,
884 platform_value!({
885 "$formatVersion": "0",
886 "canBeDeleted": true,
887 "readonly": true,
888 "keepsHistory": true,
889 "documentsKeepHistoryContractDefault": true,
890 "documentsMutableContractDefault": false,
891 "documentsCanBeDeletedContractDefault": false,
892 "requiresIdentityEncryptionBoundedKey": 0u8,
893 "requiresIdentityDecryptionBoundedKey": 1u8,
894 })
895 );
896 let recovered = DataContractConfig::from_object(value).expect("from_object");
897 assert_eq!(original, recovered);
898 }
899}