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